Irbisa · Cheatsheet

TypeScript with React

Typing props, children and events, generic components, discriminated unions, utility types, typing hooks and context, strict mode

Middle Developer · 21 questions

  1. 01

    How do you type a component's props, and do you need React.FC for it?

    Describe the props with an interface or a type alias and annotate the function's props parameter; TypeScript then checks every place the component is used.

    interface ButtonProps {  label: string;  variant?: 'primary' | 'secondary';  disabled?: boolean;  onClick: () => void;}…
  2. 02

    How do you type children, and what is the difference between ReactNode, ReactElement and JSX.Element?

    Type children as React.ReactNode when the component accepts anything React can render: strings, numbers, elements, arrays, fragments, null, undefined and booleans.

    interface CardProps {  title: string;  children: React.ReactNode; // text, elements, arrays, null} function Card({ title, children }: CardProps) {…
  3. 03

    How do you type event handlers such as onChange, onSubmit and onClick?

    Inline handlers need no annotation: TypeScript infers the event type from the JSX attribute.

    function SignupForm() {  const [email, setEmail] = useState('');   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {    setEmail(e.target.value);  };…
  4. 04

    How do you type useState when the initial value does not describe every possible state?

    Pass the type explicitly as a generic: useState<User | null>(null), useState<string[]>([]).

    interface User {  id: string;  name: string;} function Profile() {…
  5. 05

    How do you type a DOM ref, and how does a component accept a ref in React 19?

    Type a DOM ref with the element and an initial null: useRef<HTMLInputElement>(null), whose .current is HTMLInputElement | null, so TypeScript makes you check it.

    interface TextInputProps extends React.ComponentProps<'input'> {  label: string;} // React 19: ref arrives as a regular prop, no forwardReffunction TextInput({ label, ref, ...rest }: TextInputProps) {…
  6. 06

    How do you type a wrapper component that accepts all the props of a native element, such as a Button over <button>?

    Build the props from React.ComponentProps<'button'> and intersect your own: the wrapper then accepts every attribute, event and ref that <button> does, and you spread the rest onto the element.

    type ButtonProps = React.ComponentProps<'button'> & {  variant?: 'primary' | 'ghost';}; function Button({ variant = 'primary', className, ...rest }: ButtonProps) {  return <button className={`btn btn-${variant} ${className ?? ''}`} {...rest} />;…
  7. 07

    What is a discriminated union, and how does it help type component state and props?

    A discriminated union is a union of object types that share a literal field (the discriminant, such as status), so checking that field narrows the value to one member.

    type FetchState<T> =  | { status: 'idle' }  | { status: 'loading' }  | { status: 'error'; error: Error }  | { status: 'success'; data: T };…
  8. 08

    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>;}

    It does not compile: const { kind, href } = props reports "Property 'href' does not exist on type 'ActionProps'".

  9. 09

    How do you write a generic component, for example a list or a select that works with any item type?

    Declare the component as a generic function, function List<T>(props: ListProps<T>), and use T in the props.

    interface ListProps<T> {  items: T[];  getKey: (item: T) => string;  renderItem: (item: T) => React.ReactNode;}…
  10. 10

    How do you type a higher-order component so that callers do not have to pass the props it injects?

    Make the HOC generic over the wrapped component's props, P extends InjectedProps, and give the returned component Omit<P, keyof InjectedProps>.

    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>) {…
  11. 11

    How do you type a React context so that consumers do not need a null check every time?

    Create the context as createContext<Value | null>(null) and expose it only through a custom hook that throws when the provider is missing.

    interface AuthContextValue {  user: User | null;  signOut: () => void;} const AuthContext = createContext<AuthContextValue | null>(null);…
  12. 12

    How do you type useReducer actions, and how do you make the compiler catch an unhandled action?

    Type the actions as a discriminated union on type, annotate the reducer's state, action and return type, and let useReducer infer the rest from the reducer.

    type State = { count: number; step: number };type Action =  | { type: 'increment' }  | { type: 'setStep'; step: number }  | { type: 'reset' };…
  13. 13

    How do you type Redux Toolkit's useSelector and useDispatch once for the whole app, and where do RootState and AppDispatch come from?

    Infer both from the store, RootState = ReturnType<typeof store.getState> and AppDispatch = typeof store.dispatch, then create pre-typed hooks once with useSelector.withTypes<RootState>() and useDispatch.withTypes<AppDispatch>() and use only those in components.

    // 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…
  14. 14

    Which TypeScript utility types do you use most in React code, and what for?

    The everyday set is Partial, Pick, Omit, Record, ReturnType, Parameters, Awaited and NonNullable, plus React's own ComponentProps.

    interface User {  id: string;  name: string;  email: string;  role: 'admin' | 'member';}…
  15. 15

    What does the satisfies operator do, and how is it different from a type annotation and from as?

    value satisfies T checks that the value matches T but keeps the value's own, narrower inferred type.

    type Route = { path: string; title: string }; // annotation: checked, but keys widen to stringconst annotated: Record<string, Route> = {  home: { path: '/', title: 'Home' },};…
  16. 16

    How do you type the return value of a custom hook that returns a tuple like useState does?

    Return the array as const or declare the tuple return type explicitly.

    // wrong: inferred as (boolean | (() => void))[]function useToggleLoose(initial = false) {  const [on, setOn] = useState(initial);  return [on, () => setOn((v) => !v)];}…
  17. 17

    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>;}

    as User only tells the compiler what the JSON looks like; nothing checks it at runtime, and the API returns users without an address.

  18. 18

    What does strict in tsconfig.json turn on, and which extra flags are worth enabling in a React project?

    "strict": true enables a family of checks, most importantly strictNullChecks (null and undefined are separate types you must handle) and noImplicitAny (an untyped parameter is an error, not a silent any).

    {  "compilerOptions": {    "target": "ES2022",    "lib": ["ES2022", "DOM", "DOM.Iterable"],    "module": "ESNext",    "moduleResolution": "bundler",…
  19. 19

    How would you type a polymorphic component with an as prop, so that <Text as="a"> accepts href and <Text as="span"> does not?

    Make the component generic over the element type, E extends React.ElementType, and build its props as your own props plus React.ComponentProps<E> minus the keys you define.

    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'>({…
  20. 20

    How do you type props that must be either controlled (value with onChange) or uncontrolled (defaultValue), so that mixing them fails to compile?

    Model the two modes as a union and forbid the other mode's props with optional never fields: the controlled member requires value and onChange and has defaultValue?: never, the uncontrolled one has value?: never.

    type Controlled = {  value: string;  onChange: (value: string) => void;  defaultValue?: never;};…
  21. 21

    Find the bug: after the props are wrapped in Omit<ButtonProps, 'className'>, props.href no longer exists even inside the variant === '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>;}

    Omit is not distributive: it keeps only the keys that every member of the union shares, so Omit<ButtonProps, 'className'> collapses the union into { variant: 'link' | 'action' }, and href and onClick disappear.