Irbisa · Cheatsheet
TypeScript with React
Typing props, children and events, generic components, discriminated unions, utility types, typing hooks and context, strict mode
How do you type a component's props, and do you need
React.FCfor it?interface ButtonProps { label: string; variant?: 'primary' | 'secondary'; disabled?: boolean; onClick: () => void;}…How do you type
children, and what is the difference betweenReactNode,ReactElementandJSX.Element?interface CardProps { title: string; children: React.ReactNode; // text, elements, arrays, null} function Card({ title, children }: CardProps) {…How do you type event handlers such as
onChange,onSubmitandonClick?function SignupForm() { const [email, setEmail] = useState(''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setEmail(e.target.value); };…How do you type
useStatewhen the initial value does not describe every possible state?interface User { id: string; name: string;} function Profile() {…How do you type a DOM ref, and how does a component accept a
refin React 19?interface TextInputProps extends React.ComponentProps<'input'> { label: string;} // React 19: ref arrives as a regular prop, no forwardReffunction TextInput({ label, ref, ...rest }: TextInputProps) {…How do you type a wrapper component that accepts all the props of a native element, such as a
Buttonover<button>?type ButtonProps = React.ComponentProps<'button'> & { variant?: 'primary' | 'ghost';}; function Button({ variant = 'primary', className, ...rest }: ButtonProps) { return <button className={`btn btn-${variant} ${className ?? ''}`} {...rest} />;…What is a discriminated union, and how does it help type component state and props?
type FetchState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'error'; error: Error } | { status: 'success'; data: T };…Does this component compile? If not, what does TypeScript report and how do you fix it?
type ActionProps = | { kind: 'link'; href: string } | { kind: 'button'; onClick: () => void }; function Action(props: ActionProps) { const { kind, href } = props; if (kind === 'link') { return <a href={href}>Open</a>; } return <button onClick={props.onClick}>Run</button>;}How do you write a generic component, for example a list or a select that works with any item type?
interface ListProps<T> { items: T[]; getKey: (item: T) => string; renderItem: (item: T) => React.ReactNode;}…How do you type a higher-order component so that callers do not have to pass the props it injects?
interface WithUserProps { user: User;} // P is the wrapped component's full props; the HOC supplies `user` itselffunction withUser<P extends WithUserProps>(Component: React.ComponentType<P>) {…How do you type a React context so that consumers do not need a null check every time?
interface AuthContextValue { user: User | null; signOut: () => void;} const AuthContext = createContext<AuthContextValue | null>(null);…How do you type
useReduceractions, and how do you make the compiler catch an unhandled action?type State = { count: number; step: number };type Action = | { type: 'increment' } | { type: 'setStep'; step: number } | { type: 'reset' };…How do you type Redux Toolkit's
useSelectoranduseDispatchonce for the whole app, and where doRootStateandAppDispatchcome from?// app/store.tsexport const store = configureStore({ reducer: { cart: cartSlice.reducer, user: userSlice.reducer },});export type RootState = ReturnType<typeof store.getState>; // grows with every sliceexport type AppDispatch = typeof store.dispatch; // includes the thunk overloads…Which TypeScript utility types do you use most in React code, and what for?
interface User { id: string; name: string; email: string; role: 'admin' | 'member';}…What does the
satisfiesoperator do, and how is it different from a type annotation and fromas?type Route = { path: string; title: string }; // annotation: checked, but keys widen to stringconst annotated: Record<string, Route> = { home: { path: '/', title: 'Home' },};…How do you type the return value of a custom hook that returns a tuple like
useStatedoes?// wrong: inferred as (boolean | (() => void))[]function useToggleLoose(initial = false) { const [on, setOn] = useState(initial); return [on, () => setOn((v) => !v)];}…Find the bug: the profile page crashes with
Cannot read properties of undefined (reading 'city')although TypeScript reports no errors.interface User { id: string; name: string; address: { city: string };} async function loadUser(id: string): Promise<User> { const res = await fetch(`/api/users/${id}`); return (await res.json()) as User;} function City({ user }: { user: User }) { return <p>{user.address.city}</p>;}What does
strictintsconfig.jsonturn on, and which extra flags are worth enabling in a React project?{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler",…How would you type a polymorphic component with an
asprop, so that<Text as="a">acceptshrefand<Text as="span">does not?type PolymorphicProps<E extends React.ElementType, P> = P & { as?: E } & Omit<React.ComponentProps<E>, keyof P | 'as'>; type TextOwnProps = { size?: 'sm' | 'md' | 'lg' }; function Text<E extends React.ElementType = 'span'>({…How do you type props that must be either controlled (
valuewithonChange) or uncontrolled (defaultValue), so that mixing them fails to compile?type Controlled = { value: string; onChange: (value: string) => void; defaultValue?: never;};…Find the bug: after the props are wrapped in
Omit<ButtonProps, 'className'>,props.hrefno longer exists even inside thevariant === 'link'branch.type ButtonProps = | { variant: 'link'; href: string; className?: string } | { variant: 'action'; onClick: () => void; className?: string }; // the primary button sets its own classNametype PrimaryButtonProps = Omit<ButtonProps, 'className'>; function PrimaryButton(props: PrimaryButtonProps) { if (props.variant === 'link') { return <a className="btn-primary" href={props.href}>Open</a>; } return <button className="btn-primary" onClick={props.onClick}>Run</button>;}