TypeScript With React

Type React components, prop interfaces, event handlers, hooks, and DOM refs with full static safety.

TL;DR

  1. Define component contracts using strongly typed interface props declarations.
  2. Type synthetic DOM event handlers using React.ChangeEvent parameters.
  3. Manage nullable state in hooks using explicit useState generics.

Component Props and Typing

    Interface props definition

    Define typed props with optional defaults.

    interface ButtonProps {
      label: string;
      onClick: () => void;
    }
    function Button({ label, onClick }: ButtonProps) {
      return <button onClick={onClick}>{label}</button>;
    }
    Native HTML prop extension

    Inherit standard native button attributes.

    import type { ComponentPropsWithoutRef } from "react";
    interface BaseBtnProps
      extends ComponentPropsWithoutRef<"button"> {
      variant: "primary" | "secondary";
    }
    Typed children prop

    Accept any renderable React node child.

    import type { ReactNode } from "react";
    interface CardProps {
      title: string;
      children: ReactNode;
    }
    function Card({ title, children }: CardProps) {
      return (
        <SectionCard title={title}>
          {children}
        </SectionCard>
      );
    }

Event Handlers

    Input change handler

    Read a typed text input value.

    import type { ChangeEvent } from "react";
    const onInput = (e: ChangeEvent<HTMLInputElement>) =>
      e.currentTarget.value;
    Form submit handler

    Prevent reload with a typed form event.

    import type { FormEvent } from "react";
    const onSubmit = (e: FormEvent<HTMLFormElement>) =>
      e.preventDefault();
    Mouse click handler

    Read a button click position.

    import type { MouseEvent } from "react";
    const onClick = (e: MouseEvent<HTMLButtonElement>) =>
      console.log(e.clientX, e.clientY);
    Keyboard event handler

    Check for Enter in a typed input event.

    import type { KeyboardEvent } from "react";
    const onKey = (e: KeyboardEvent<HTMLInputElement>) =>
      e.key === "Enter" && console.log("Enter");

Typing React Hooks

    DOM element ref hook

    Type DOM reference with HTML element.

    import { useRef } from "react";
    const inputRef = useRef<HTMLInputElement>(null);
    function focus() {
      inputRef.current?.focus();
    }
    Typed useReducer hook

    Use discriminated union actions in reducers.

    type Action =
      | { type: "increment" }
      | { type: "set"; value: number };
    function reducer(
      state: number,
      action: Action
    ): number {
      if (action.type === "set") return action.value;
      return state + 1;
    }
    Custom hook tuple return

    Freeze return array using as const.

    import { useState } from "react";
    function useToggle(initial = false) {
      const [val, setVal] = useState(initial);
      const toggle = () => setVal(v => !v);
      return [val, toggle] as const;
    }

Context and Forward Ref

    Typed context creation

    Create context with undefined fallback.

    import { createContext, useContext } from "react";
    interface ThemeCtx { dark: boolean; }
    const Ctx = createContext<ThemeCtx | undefined>(
      undefined
    );
    function useTheme() {
      const c = useContext(Ctx);
      if (!c) throw new Error("Missing ThemeProvider");
      return c;
    }
    Typed forward ref component

    Expose underlying input DOM node to callers.

    import { forwardRef } from "react";
    type Props = { label: string };
    const CustomInput = forwardRef<
      HTMLInputElement,
      Props
    >(({ label }, ref) => (
      <label>
        {label}
        <input ref={ref} />
      </label>
    ));
    RefObject vs MutableRefObject

    Compare read-only DOM and mutable value refs.

    import { useRef } from "react";
    const divRef = useRef<HTMLDivElement>(null); // read-only
    const countRef = useRef(0); // writable
    countRef.current++;

TSConfig Configuration for React

    Standard JSX transform

    Use react-jsx without manual React imports.

    {
      "compilerOptions": {
        "jsx": "react-jsx"
      }
    }
    DOM library types

    Include browser DOM types in lib array.

    {
      "compilerOptions": {
        "lib": ["DOM", "DOM.Iterable", "ESNext"]
      }
    }
    Component path aliases

    Configure clean alias paths for components.

    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@components/*": ["src/components/*"]
        }
      }
    }

Tips

  1. Extend React.ComponentPropsWithoutRef when wrapping native HTML tags to forward standard attributes like className and onClick automatically.
  2. Use React.ReactNode for children props to support JSX elements, strings, numbers, portals, or conditional null rendering cleanly.

Warnings

  1. Avoid legacy React.FC typing in component declarations, as it enforces unnecessary wrapper abstractions and historical implicit children typing problems.
  2. Never assign any to event handler parameters; use specialized types like React.MouseEvent to preserve target property autocompletion.

In Practice

FAQ