Best Practices for Typing React Components with TypeScript
Guidelines and patterns for typing props, children, higher-order components, and hooks in React + TypeScript to improve DX and avoid common pitfalls.
Best Practices for Typing React Components with TypeScript
Intro: React and TypeScript together unlock excellent dev ergonomics, but there are traps — particularly around generics, prop inference, and children. This post offers concrete patterns and recipes to type components robustly.
“Types should simplify component contracts, not complicate them.”
1. Prefer function components with typed props
Use explicit prop interfaces instead of relying on implicit any or uncontrolled prop shapes:
type ButtonProps = {
label: string;
onClick?: () => void;
};
function Button({label, onClick}: ButtonProps) {
return ;
}
2. Typing children
When components expect children, type them with React.ReactNode for generality or use ReactElement for stricter element types:
type PanelProps = {
children?: React.ReactNode;
};
3. Generic components — be explicit
Generics let you build reusable typed components (e.g., typed lists). Use generics sparingly and provide defaults to avoid forcing callers to supply type arguments.
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
};
function List<T, >({items, renderItem}: ListProps<T>) {
return <div>{items.map(renderItem)}</div>
}
4. Default props and optionality
Prefer optional props with sensible defaults inside the component instead of relying on React's legacy defaultProps behavior with function components. This avoids typing gotchas:
function Heading({size = 'md'}: {size?: 'sm'|'md'|'lg'}) { ... }
5. Discriminated unions for conditional props
When a component can accept mutually exclusive prop sets, use discriminated unions:
type LinkAnchor = {type: 'a'; href: string}
type ButtonAction = {type: 'button'; onClick: () => void}
type ActionProps = LinkAnchor | ButtonAction;
function Action(props: ActionProps) {
if (props.type === 'a') return <a href={props.href}>Link</a>
return <button onClick={props.onClick}>Button</button>
}
6. Higher-order components and forwardRef
Typing HOCs and refs is tricky. For HOCs, preserve generics and prop intersections. For refs, use forwardRef<Element, Props> and ensure ref types are accurate.
7. Hooks with typed state and returns
Explicitly type hook return shapes and state to avoid implicit anys in callbacks and effects.
function useCounter(initial = 0): [number, {inc: () => void; dec: () => void}] { ... }
8. Avoid over-typing DOM APIs
When using DOM refs, prefer the built-in types like HTMLInputElement instead of any. Use event types such as React.ChangeEvent<HTMLInputElement> for event handlers.
9. Utility types for props
Use Omit or Pick to derive prop types when wrapping components, e.g., removing internal props from public APIs.
10. Testing components
When writing tests, rely on the same component types to ensure props passed in tests match the component's contract. Type-driven tests can catch regressions early.
Conclusion
TypeScript improves React development when types are used to capture intent, not to micromanage implementation. Favor clear prop contracts, discriminated unions for variant patterns, and minimal, well-documented generics. As a rule of thumb, if a type costs more to maintain than the value it provides, simplify it.
Action: Audit three core components in your UI library and apply these patterns to reduce runtime bugs and improve editor feedback.
Related Topics
Sofia Martinez
Frontend Architect
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you