TypeScript · Types that pay for themselves
How do you type React components with TypeScript?
Short answer
React with TypeScript means typing props, state, events, refs and context. Type the public props of components, use ReactNode for children and prefer inferred types for local state and event handlers.
The main design decision is where to put type annotations. Public props deserve an explicit type because they are the component's contract. Local state, derived values and inline event handlers are usually inferred. Generic components are useful for reusable lists and inputs where the item type should flow through.
import type { ReactNode } from "react";
type ButtonProps = {
label: string;
onClick: () => void;
children?: ReactNode;
};
function Button({ label, onClick, children }: ButtonProps) {
return (
<button onClick={onClick}>
{label}
{children}
</button>
);
}Patterns worth knowing
- Use ReactNode for children rather than JSX.Element
- Use ComponentProps<"button"> to extend native element props
- Type useState explicitly only when the initial value is too narrow
- Type event handlers with ChangeEvent or MouseEvent when extracted
- Use a generic component when the item type must flow into callbacks
Avoid typing children as JSX.Element. A component can receive strings, numbers, arrays, null or fragments, and ReactNode covers those cases. For DOM wrappers, extending ComponentProps keeps the native attributes and event types without re-declaring them.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1Why use ReactNode instead of JSX.Element for children?
- 2How do you type a component that forwards native button props?
- 3When do you need to annotate useState?
- 4How do you type an extracted event handler?
Say it in the interview
With React and TypeScript I type the public props of a component and let local state and event handlers infer. I use ReactNode for children, ComponentProps for wrapping DOM elements, and explicit event types only when the handler is extracted. The goal is to make the component's contract clear without annotating every internal variable. I also avoid casting DOM results and instead check for null.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live TypeScript mock interview and get scored feedback on depth, clarity and confidence.