TypeScript React Components
MedTypeScript and React are a natural pairing. Typing component props ensures that consumers pass the correct data, and TypeScript catches missing or wrong props at compile time. Understanding how to type functional components, children, event handlers, and common React patterns is essential for any modern React project.
Interactive Visualization
Key Points
- Define props as an interface: `interface ButtonProps { label: string; onClick: () => void }`
- Use `React.FC<Props>` sparingly — prefer explicit return types or no annotation
- Type children with `React.ReactNode` for the broadest JSX-compatible type
- Event handlers use React synthetic event types: `React.MouseEvent<HTMLButtonElement>`
- Discriminated union props enable type-safe component variants
- Use `React.ComponentPropsWithoutRef<"button">` to inherit native HTML props
- Generic components use generics in the function signature for type-safe data rendering
Code Examples
Component Props & Events
interface ButtonProps { label: string variant?: 'primary' | 'secondary' | 'danger' disabled?: boolean onClick: (event: React.MouseEvent<HTMLButtonElement>) => void } function Button({ label, variant = 'primary', disabled, onClick }: ButtonProps) { return ( <button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick} > {label} </button> ) } // Extending native HTML props type InputProps = React.ComponentPropsWithoutRef<'input'> & { label: string error?: string } function Input({ label, error, ...rest }: InputProps) { return ( <label> {label} <input {...rest} /> {error && <span className="error">{error}</span>} </label> ) }
Props interfaces define the contract between a component and its consumers. Extending native HTML props with ComponentPropsWithoutRef avoids redefining standard attributes.
Children & Composition
// ReactNode is the broadest children type interface CardProps { title: string children: React.ReactNode } function Card({ title, children }: CardProps) { return ( <div className="card"> <h2>{title}</h2> <div className="card-body">{children}</div> </div> ) } // Render prop pattern with TypeScript interface DataFetcherProps<T> { url: string children: (data: T, loading: boolean) => React.ReactNode } function DataFetcher<T>({ url, children }: DataFetcherProps<T>) { const [data, setData] = React.useState<T | null>(null) const [loading, setLoading] = React.useState(true) React.useEffect(() => { fetch(url) .then(r => r.json() as Promise<T>) .then(d => { setData(d); setLoading(false) }) }, [url]) return <>{data && children(data, loading)}</> }
React.ReactNode accepts anything renderable: elements, strings, numbers, fragments, null. The render prop pattern benefits greatly from generics to type the data being passed.
Discriminated Union Props
// Props that depend on a variant discriminant type AlertProps = | { variant: 'success'; message: string } | { variant: 'error'; message: string; retryAction: () => void } | { variant: 'loading' } function Alert(props: AlertProps) { switch (props.variant) { case 'success': return <div className="alert-success">{props.message}</div> case 'error': return ( <div className="alert-error"> {props.message} <button onClick={props.retryAction}>Retry</button> </div> ) case 'loading': return <div className="alert-loading">Loading...</div> } } // Usage <Alert variant="error" message="Failed" retryAction={() => {}} /> // <Alert variant="loading" message="hi" /> // Error: message not on loading
Discriminated union props ensure that variant-specific props are only available when the matching variant is selected. TypeScript enforces this at compile time.
Common Mistakes
- Using `React.FC` which adds implicit children and complicates generic components
- Typing children as `JSX.Element` instead of `React.ReactNode` (too restrictive)
- Forgetting to type event handlers — `onClick: () => void` misses the event parameter
- Not using `ComponentPropsWithoutRef` when wrapping native HTML elements
- Creating monolithic props interfaces instead of using discriminated unions for variants
Interview Tips
- Know the difference between ReactNode, ReactElement, and JSX.Element
- Explain why `React.FC` is often avoided in modern TypeScript React codebases
- Demonstrate generic components — they show advanced TypeScript + React knowledge
- Mention discriminated union props for type-safe component variants