Chapter 1 of 4
Typing Props
The base patterns for component props, including children.
A component's props are just an object, so you type them like any other object. Most codebases declare an interface or a type alias and annotate the parameter.
interface ButtonProps {
label: string;
variant?: "primary" | "ghost"; // optional, with a union of allowed values
onClick: () => void;
}
function Button({ label, variant = "primary", onClick }: ButtonProps) {
return <button className={variant} onClick={onClick}>{label}</button>;
}interface or type?
For props either works. interface can be extended and merged; type can express unions and intersections. Pick one for consistency - many teams use interface for props and type for everything else.
Typing children
ReactNode covers everything React can render: elements, strings, numbers, arrays, null and undefined.
interface CardProps {
title: string;
children: React.ReactNode;
}
// Optional children
interface PanelProps {
children?: React.ReactNode;
}// Avoid
const Button: React.FC<ButtonProps> = ({ label }) => <button>{label}</button>;
// Prefer
function Button({ label }: ButtonProps) {
return <button>{label}</button>;
}Extending an HTML element's props
A wrapper component should usually accept everything the underlying element does. ComponentProps pulls those types in for you.
interface ButtonProps extends React.ComponentProps<"button"> {
variant?: "primary" | "ghost";
}
function Button({ variant = "primary", ...rest }: ButtonProps) {
return <button className={variant} {...rest} />;
}
// onClick, disabled, type, aria-label - all typed correctlyUnions for mutually exclusive props
type AlertProps =
| { severity: "error"; retry: () => void }
| { severity: "info"; retry?: never };
// TypeScript now requires retry for errors and forbids it for info