CodeOath
← All posts
TypeScript75 min total · 21 parts

TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime

Contents — Part 20 of 21: TypeScript with React
Part 20 of 21 · ~1 min

TypeScript with React

Most real React codebases today are written in TypeScript rather than plain JavaScript — see React Fundamentals for the component/hooks side of that pairing. A few patterns specific to combining the two:

interface CardProps {
  title: string;
  onClose?: () => void; // optional prop
  children: React.ReactNode; // the type for "anything React can render," including strings and other elements
}

function Card({ title, onClose, children }: CardProps) {
  return (
    <div className="card">
      <h3>{title}</h3>
      {children}
      {onClose && <button onClick={onClose}>Close</button>}
    </div>
  );
}

Typing hooks correctly is where most of the friction shows up in practice:

// useState: TS infers from the initial value when possible, but an explicit type
// argument is needed when the initial value doesn't cover every later possibility
const [user, setUser] = useState<User | null>(null);

// useRef targeting a DOM node needs an explicit element type, initialized to null
const inputRef = useRef<HTMLInputElement>(null);

// A typed event handler, instead of an untyped inline function
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  console.log(e.target.value); // e.target is correctly typed as HTMLInputElement, with .value
}

useState<User | null>(null) is the common pattern for state that starts empty and is filled in later (e.g., after a fetch resolves) — without the explicit type argument, TypeScript would infer the state's type as just null from the initial value, and never allow assigning an actual User to it later.