TypeScript with React
Practical typing patterns for components, hooks, and events under strict TypeScript.
Search across all documentation pages
Practical typing patterns for components, hooks, and events under strict TypeScript.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Declare props as a named type or interface and annotate the parameter. Export the type when parents construct props objects.
type AvatarProps = { src: string; alt: string; size?: number };
function Avatar({ src, alt, size = 40 }: AvatarProps) {
return <img src={src} alt={alt} width={size} height={size} />;
}Use React.ReactNode for anything renderable as children. Narrow only when you truly require a single element.
type BoxProps = { children: React.ReactNode };
function Box({ children }: BoxProps) {
return <div className="box">{children}</div>;
}Prefer React's synthetic event types. ChangeEvent for inputs, FormEvent for forms, MouseEvent for clicks.
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}Infer when the initial value is precise; pass a generic when the initial value is wider (e.g. null union).
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState(0); // inferred numberType host refs with the concrete element interface. Start as null until React attaches the node.
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();For mutable boxes that always hold a value after init, use a definite type and assign .current yourself.
const idRef = useRef(0);
idRef.current += 1;React.ComponentProps<"button"> pulls the prop type of a host tag or another component for clean extension.
type BtnProps = React.ComponentProps<"button"> & { loading?: boolean };Utility that adds optional children to your props type when you do not want to declare it manually.
type CardProps = React.PropsWithChildren<{ title: string }>;Model variants with a shared discriminant so TypeScript narrows related fields correctly.
type AlertProps =
| { kind: "error"; error: Error }
| { kind: "success"; message: string };
function Alert(props: AlertProps) {
if (props.kind === "error") return <p>{props.error.message}</p>;
return <p>{props.message}</p>;
}Parameterize item type so map callbacks stay typed without casting.
type ListProps<T> = {
items: T[];
getKey: (item: T) => string;
renderItem: (item: T) => React.ReactNode;
};
function List<T>({ items, getKey, renderItem }: ListProps<T>) {
return <ul>{items.map((item) => <li key={getKey(item)}>{renderItem(item)}</li>)}</ul>;
}Give createContext an explicit type. Start with null and narrow in a custom hook for required providers.
type Auth = { user: User; logout: () => void };
const AuthContext = createContext<Auth | null>(null);Read another component's props without exporting a separate type using React.ComponentProps<typeof Comp>.
type RowProps = React.ComponentProps<typeof Row>;Use as const on config objects so string fields stay literal unions instead of widening to string.
const tones = ["info", "danger", "success"] as const;
type Tone = (typeof tones)[number];satisfies checks a value against a type while preserving narrower inferred literals.
const routes = {
home: "/",
docs: "/docs",
} satisfies Record<string, string>;Prefer an explicit props parameter. React.FC historically implied children and obscures generic components.
// Prefer:
function Button(props: ButtonProps) { return <button {...props} />; }
// Avoid: const Button: React.FC<ButtonProps> = (props) => ...Stack versions: React 19 · TypeScript (strict) ·
@types/reactmatching React 19
Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥