- React
- Type-Safe React: Components, Generics, and Hooks
- Typing Components, Props, Children & Events
Typing Components, Props, Children & Events
In modern React TypeScript, type components as standard functions with typed prop interfaces rather than React.FC. Type children with React.ReactNode, and use specific synthetic event types like React.MouseEvent<HTMLButtonElement> or React.ChangeEvent<HTMLInputElement> for full type safety.
Key Learning Objectives
Compare standard function typing vs React.FC and explain why React.FC is discouraged.
Differentiate ReactNode, ReactElement, and JSX.Element for typing children and render props.
Type DOM event handlers accurately with synthetic event generics and HTML element targets.
Apply ComponentPropsWithoutRef to extend native HTML button, input, and div elements safely.
The Interview Problem & Context
Provide production TypeScript signatures for a custom Button component, an Input with event handlers, and explain why `React.ReactNode` is preferred over `JSX.Element` for children.
1import React, { ComponentPropsWithoutRef, ReactNode, MouseEvent, ChangeEvent } from 'react';23// 1. Extending native HTML button props cleanly4interface ButtonProps extends ComponentPropsWithoutRef<'button'> {5 variant?: 'primary' | 'secondary' | 'danger';6 isLoading?: boolean;7 children: ReactNode; // Accepts JSX, strings, numbers, fragments, portals, or null8}910// 2. Idiomatic Function Component (Avoid React.FC)11export function CustomButton({12 variant = 'primary',13 isLoading = false,14 children,15 onClick,16 ...nativeProps17}: ButtonProps) {18 const handleClick = (e: MouseEvent<HTMLButtonElement>) => {19 if (isLoading) {20 e.preventDefault();21 return;22 }23 onClick?.(e);24 };2526 return (27 <button {...nativeProps} onClick={handleClick} disabled={isLoading || nativeProps.disabled}>28 {isLoading ? 'Loading...' : children}29 </button>30 );31}3233// 3. Typing Form Inputs and Change Events34interface SearchInputProps {35 value: string;36 onChange: (e: ChangeEvent<HTMLInputElement>) => void;37 onSearchSubmit: (query: string) => void;38}3940export function SearchInput({ value, onChange, onSearchSubmit }: SearchInputProps) {41 return (42 <input43 type="text"44 value={value}45 onChange={onChange}46 onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {47 if (e.key === 'Enter') onSearchSubmit(value);48 }}49 />50 );51}
Deep Architectural Explanation
Idiomatic React Component Typing
1. Why Avoid React.FC (React.FunctionComponent):
- In older React versions,
React.FCautomatically added an implicitchildren?: ReactNodeprop, breaking components that were never intended to receive children. - It complicates generic components (
function List<T>()). - Writing a standard function with a typed prop parameter is simpler and more idiomatic:
export function MyButton(props: ButtonProps) {}
2. ReactNode vs ReactElement vs JSX.Element:
| Type | Allowed Values | When to Use |
|---|---|---|
React.ReactNode | JSX.Element, string, number, boolean, null, undefined, Iterable<ReactNode> | Best for children prop and slot props. |
React.ReactElement | Valid React element object created via React.createElement | When component MUST return or receive a single JSX element. |
JSX.Element | ReactElement with any props and any type | Returned by JSX expressions; generally use ReactElement instead. |
3. Typing Events:
Always use React SyntheticEvent generics:
React.MouseEvent<HTMLButtonElement>React.ChangeEvent<HTMLInputElement>React.KeyboardEvent<HTMLInputElement>React.FormEvent<HTMLFormElement>
Common Interview Traps & Pitfalls
Typing event handlers as `(e: any) => void` instead of using React synthetic event generics.
Typing children as `JSX.Element`, which rejects plain strings, numbers, or null.
Using `React.FC` which creates unnecessary type indirection.
Senior Interviewer Follow-Up Probes
How does `ComponentPropsWithoutRef<"button">` differ from `ButtonHTMLAttributes<HTMLButtonElement>`?
How do you type a component that accepts only a single valid React child element?
How do you type forwardRef components in TypeScript without using any?
Frequently Asked Interview Questions
What is the difference between ReactNode and JSX.Element?
ReactNode is a broad union that includes strings, numbers, booleans, null, undefined, ReactElements, and arrays of ReactNodes. JSX.Element is specifically an object representing a JSX expression and rejects strings or numbers.
How do you type HTML button props including standard HTML attributes?
Use ComponentPropsWithoutRef<"button">. It includes all standard HTML attributes (disabled, type, aria-*, id, etc.) without having to declare them manually.
What is the correct type for an onChange handler on an input element?
React.ChangeEventHandler<HTMLInputElement> or a function accepting (event: React.ChangeEvent<HTMLInputElement>) => void.
How do you type a ref prop in React 19 vs React 18?
In React 19, ref is a regular prop typed as ref?: React.Ref<HTMLButtonElement>. In React 18 and earlier, forwardRef<HTMLButtonElement, Props> was required.
Can a React component return null in TypeScript?
Yes, returning null is valid in React and TypeScript represents this cleanly when return type is ReactNode or ReactElement | null.
