TypeScript + React — static typing for components, props, hooks, and state. Reduces bugs and improves DX.
Why it matters: TypeScript catches type-related errors at compile time rather than at runtime. In a React application with hundreds of components, this means you catch mismatched props, incorrect callback signatures, and wrong state shapes before the code ever reaches the browser. It also serves as live documentation — when you hover over a component in your editor, you see exactly what props it accepts and what types they should be.
1. Typing components:
1// Props with types2interface UserCardProps {3 name: string;4 age: number;5 avatar?: string; // Optional6 onEdit: (id: number) => void;7}89function UserCard({ name, age, avatar, onEdit }: UserCardProps) {10 return (11 <div>12 {avatar && <img src={avatar} alt={name} />}13 <h2>{name}, {age}</h2>14 <button onClick={() => onEdit(1)}>Edit</button>15 </div>16 );17}
2. Typing children:
1interface CardProps {2 title: string;3 children: React.ReactNode; // Any React element4}56// For strict checking7interface StrictCardProps {8 children: React.ReactElement; // Exactly one element9}
3. Typing hooks:
1function useUser(id: number) {2 const [user, setUser] = useState<User | null>(null);3 const [loading, setLoading] = useState(true);45 useEffect(() => {6 fetch(`/api/users/${id}`)7 .then(res => res.json())8 .then((data: User) => { setUser(data); setLoading(false); });9 }, [id]);1011 return { user, loading };12}
4. Typing events:
1function SearchInput() {2 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {3 console.log(e.target.value);4 };56 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {7 e.preventDefault();8 };910 return <form onSubmit={handleSubmit}><input onChange={handleChange} /></form>;11}
5. Generic components:
1interface ListProps<T> {2 items: T[];3 renderItem: (item: T) => React.ReactNode;4}56function List<T>({ items, renderItem }: ListProps<T>) {7 return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>;8}910<List items={[{ id: 1, name: "Alice" }]} renderItem={item => item.name} />
6. Typing refs:
1function TextInputWithRef() {2 const inputRef = useRef<HTMLInputElement>(null);34 const focusInput = () => {5 inputRef.current?.focus(); // null-safe6 };78 return (9 <div>10 <input ref={inputRef} type="text" />11 <button onClick={focusInput}>Focus</button>12 </div>13 );14}
7. Typing context:
1interface AuthContextType {2 user: User | null;3 login: (email: string) => Promise<void>;4 logout: () => void;5}67const AuthContext = createContext<AuthContextType | undefined>(undefined);89function useAuth(): AuthContextType {10 const context = useContext(AuthContext);11 if (!context) throw new Error("useAuth must be used within AuthProvider");12 return context;13}
Common mistakes to avoid:
any everywhere defeats the purpose of TypeScript.?.) with refs that can be null.