All TypeScript templates

TypeScript React Component Types

Component prop typing: discriminated unions, generics, polymorphic refs.

DevZone Tools1,290 copiesUpdated Apr 1, 2026TypeScriptReact
# CLAUDE.md — TypeScript React Component Types

## Prop typing

- Props are an `interface`, not a `type`, when other components might extend them.
- Required props first. Optional props after.
- Document non-obvious props with TSDoc — it shows up in editor hovers.
- Avoid optional booleans for two-state things. `kind: "primary" | "secondary"` reads better than `isPrimary?: boolean` when you have more than one variant.

## Common patterns

```ts
// Children: free-form
type Props = { children: React.ReactNode }

// Children: a single element
type Props = { children: React.ReactElement }

// Render prop
type Props = { children: (state: State) => React.ReactNode }

// Forwarded native props
type Props = React.ComponentPropsWithoutRef<"button"> & { variant: "primary" | "ghost" }
```

- `ComponentPropsWithoutRef<"button">` over `ButtonHTMLAttributes` — picks up `data-*`, `aria-*`, and event handlers automatically.

## Discriminated unions for variants

```ts
type Props =
  | { kind: "icon"; icon: React.ReactNode; label: string }
  | { kind: "label"; label: string }
```

- `kind` makes the variant explicit. `switch (props.kind)` exhausts cases.
- Don't use `XOR` types or "if A is set, B can't be" comments. Encode it in the type.

## Event handlers

- Type the event with the React-specific generic:
  ```ts
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
  ```
- For form events, narrow to the element you actually use.
- Don't type as `(e: any) => void` "for simplicity". The type cost is one extra word.

## Refs & polymorphic components

- `forwardRef` for components that wrap a DOM node and need to expose the ref:
  ```ts
  const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => ...)
  ```
- For polymorphic components (`as` prop), the typing is intricate. Use a battle-tested pattern (e.g., from radix-ui or a small util like `react-polymorphic-types`) — don't reinvent.

## Generics in components

- Components can be generic for list/select/table primitives:
  ```ts
  function Select<T>({ items, value, onChange }: { items: T[]; value: T; onChange: (v: T) => void }) { ... }
  ```
- Constrain generics with `extends` when you need a property: `<T extends { id: string }>`.

## Default values

- Default props in destructuring: `{ variant = "primary" }: Props`. No `defaultProps`.
- For boolean props, default to `false` and name positively.

## Don't

- Don't type `children` as `JSX.Element` — too restrictive (excludes strings, fragments, null).
- Don't use `React.FC` / `React.FunctionComponent`. It implies an unwanted `children` and complicates generics.
- Don't pass through every prop as `{...props}` to `<div>`. Whitelist the props that make sense.
- Don't widen prop types ("just in case"). The narrower, the better the contract.

Other TypeScript templates