file-typeEngineering

TypeScript for Frontend

Typing props and hooks, generics, narrowing, discriminated unions, and the utility types worth knowing.

1 item

Links1

01NotesNote

Type the boundaries, let inference do the rest

Annotate function signatures, component props, and API payloads. Inside a function, let TypeScript infer — over-annotating local variables is noise. The goal is types where data enters your code (props, fetch responses, form input), so everything downstream is checked for free.

Typing components

type ButtonProps = {
  variant: "primary" | "ghost";
  disabled?: boolean;
  onClick: () => void;
  children: React.ReactNode;
};

function Button({ variant, disabled = false, onClick, children }: ButtonProps) { ... }
  • Use React.ReactNode for children, not string.
  • Prefer union literals ("primary" | "ghost") over string — they document valid values and autocomplete.
  • For props that extend a DOM element, intersect with the element's props: type Props = { variant: ... } & React.ComponentProps<"button">.

Discriminated unions — the most useful pattern

Model "states that can't coexist" so impossible states are unrepresentable:

type Result =
  | { status: "loading" }
  | { status: "error"; message: string }
  | { status: "success"; data: User };

Switching on status narrows the type — TypeScript knows data only exists in the success branch. This kills a whole class of "cannot read property of undefined" bugs.

Narrowing & guards

  • typeof, in, and custom type guards (function isUser(x): x is User) narrow unknown/union types safely.
  • Treat external data as unknown and validate it (Zod) rather than casting with as. as is a lie to the compiler — use it sparingly.

Utility types worth memorizing

  • Partial<T>, Required<T>, Pick<T, K>, Omit<T, K> for reshaping.
  • Record<K, V> for maps/dictionaries.
  • ReturnType<typeof fn> and Awaited<T> to derive types from values instead of duplicating them.
  • Derive types from a single source of truth (e.g. a Zod schema → z.infer<typeof schema>) so types and runtime validation never drift.

Config

  • Run in strict mode. The whole value of TS evaporates without it. Add noUncheckedIndexedAccess for an extra safety net on array/object access.