TypeScript for React Devs
A no-fluff guide to typing props, state, events, and async data in React.
What You'll Learn
- Type component props with interface and handle optional fields
- Use generics to build reusable components and hooks
- Correctly type DOM events, refs, and forms
- Handle async data with discriminated unions
- Set up a strict TypeScript config for React projects
Prerequisites
- JavaScript ES6+ (destructuring, spread, async/await)
- Basic React (components, hooks, props)
- TypeScript fundamentals (types, interfaces)
Step-by-Step Guide
Step 1
1. Typing Component Props
Define props with an interface, not inline. Use interface over type for props because interfaces are extensible with extends. Mark optional props with ?. For children, use React.ReactNode (accepts anything renderable). For event handlers, use React.MouseEventHandler<HTMLButtonElement> or inline (e: React.MouseEvent<HTMLButtonElement>) => void.
Step 2
2. useState & Discriminated Unions
useState<T> infers from the initial value, but be explicit for unions: const [status, setStatus] = useState<'idle'|'loading'|'error'|'success'>('idle'). This creates a discriminated union — switch/case on status gives TypeScript full narrowing. Use a type with shape { status: 'error'; message: string } | { status: 'success'; data: T } to bundle related fields.
Step 3
3. Typing DOM Events & Forms
For input onChange: (e: React.ChangeEvent<HTMLInputElement>) => void. For form onSubmit: (e: React.FormEvent<HTMLFormElement>) => void — call e.preventDefault() inside. For refs: const inputRef = useRef<HTMLInputElement>(null) — note the null initial value and the required null check before accessing inputRef.current?.focus().
Step 4
4. Generic Components & Hooks
Generic components accept a type parameter: function List<T extends { id: string }>({ items, render }: { items: T[]; render: (item: T) => React.ReactNode }). Generic hooks let callers specify the return type: function useFetch<T>(url: string): { data: T | null; loading: boolean; error: string | null }. This makes your utilities composable and type-safe.
Step 5
5. Strict Config & Common Pitfalls
Enable strict: true in tsconfig.json — this catches the most bugs. Avoid casting with as unless you have no alternative (use type guards instead). Don't use any; prefer unknown and narrow it. Avoid React.FC for components — it hides the return type and adds implicit children. Use function declarations or const with an explicit return type annotation.
Further Reading & Resources
Ready to put this into practice?
Apply for a real Web Development internship and ship production projects with mentorship.
