import { ReactNode } from "react";type Status = "loading" | "error" | "success";const StatusView = ({ status }: { status: Status }) => { let content: ReactNode; if (status === "loading") { content = <p>Loading...</p>; } else if (status === "error") { content = <p style={{ color: "red" }}>An error occurred</p>; } else { content = <p>Done!</p>; } return <div>{content}</div>;};
Early Return (Guard Clause)
const UserProfile = ({ user }: { user: User | null }) => { if (!user) return <p>User not found</p>; if (!user.isActive) return <p>This user is inactive</p>; return ( <div> <h2>{user.name}</h2> <p>{user.email}</p> </div> );};
Return null to Render Nothing
const Banner = ({ show }: { show: boolean }) => { if (!show) return null; return <div className="banner">Announcement!</div>;};
// Do not use index as key when list items can be sorted, added, or removed<ul> {items.map((item, index) => ( <li key={index}>{item.name}</li> ))}</ul>// Use a stable, unique ID (recommended)<ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))}</ul>// index is acceptable only for static lists where order never changes
Extracting to a Component / Empty List Handling
// When splitting into a separate component, put key on the call siteconst ItemCard = ({ item }: { item: Item }) => <li>{item.name}</li>;const ItemList = ({ items }: { items: Item[] }) => { if (items.length === 0) return <p>No items found</p>; return ( <ul> {items.map((item) => ( <ItemCard key={item.id} item={item} /> ))} </ul> );};
6. useState
// Type inference works when initial value is obviousconst [count, setCount] = useState(0);const [name, setName] = useState("Alice");// Type definition (define the type before useState when explicit)type User = { id: number; name: string; email: string;};// Explicit type annotationconst [user, setUser] = useState<User | null>(null);const [items, setItems] = useState<string[]>([]);// Update examplessetUser({ id: 1, name: "Bob", email: "bob@example.com" });setUser(null);// Functional updatesetCount((prev) => prev + 1);
7. useEffect
import { useEffect, useState } from "react";// BasicuseEffect(() => { console.log("Runs on mount");}, []);// With dependency arrayuseEffect(() => { console.log("Runs when count changes:", count);}, [count]);// CleanupuseEffect(() => { const timer = setInterval(() => { setCount((c) => c + 1); }, 1000); return () => clearInterval(timer); // Runs on unmount}, []);// Async (cannot use async directly in useEffect)useEffect(() => { const fetchData = async () => { const res = await fetch("/api/data"); const json = await res.json(); setData(json); }; fetchData();}, []);
8. useRef
import { useRef } from "react";// DOM referenceconst inputRef = useRef<HTMLInputElement>(null);const handleFocus = () => { inputRef.current?.focus(); // Safe access with optional chaining};// Mutable value (does not trigger re-render)const countRef = useRef<number>(0);countRef.current += 1;// Usage in JSX<input ref={inputRef} type="text" />;
type User = { id: number; name: string; email: string; age: number;};// Partial: make all properties optionaltype PartialUser = Partial<User>;// → { id?: number; name?: string; ... }// Required: make all properties requiredtype RequiredUser = Required<PartialUser>;// Pick: extract specific propertiestype UserPreview = Pick<User, "id" | "name">;// → { id: number; name: string }// Omit: exclude specific propertiestype UserWithoutEmail = Omit<User, "email">;// → { id: number; name: string; age: number }// Readonly: make all properties read-onlytype ReadonlyUser = Readonly<User>;// Record: object type with specified key and value typestype RoleMap = Record<"admin" | "user" | "guest", boolean>;// ReturnType: get the return type of a functionconst getUser = () => ({ id: 1, name: "Alice" });type UserType = ReturnType<typeof getUser>;// Parameters: get the parameter types of a functiontype Params = Parameters<typeof getUser>;// NonNullable: exclude null and undefinedtype MaybeString = string | null | undefined;type DefiniteString = NonNullable<MaybeString>; // string
Tips
Preserve Literal Types with as const
const COLORS = ["red", "green", "blue"] as const;type Color = (typeof COLORS)[number]; // "red" | "green" | "blue"
Prefer Type Guards Over Type Assertions
// Type assertion (unsafe)const value = someValue as string;// Type guard (safe)const isString = (value: unknown): value is string => typeof value === "string";if (isString(value)) { console.log(value.toUpperCase()); // treated as string here}