TypeScript React Component Types
Component prop typing: discriminated unions, generics, polymorphic refs.
# 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 React templates
Next.js App Router + TypeScript Rules
Server Components, Server Actions, and TypeScript discipline for the Next.js App Router.
Next.js + Server Actions + Shadcn UI
Forms with Server Actions, Zod validation, and Shadcn UI primitives.
React + TypeScript + Vite SPA Rules
Modern React SPA conventions: hooks, Suspense, error boundaries, Vite tuning.
React Hooks Best Practices
Disciplined hook usage: deps arrays, custom hooks, refs, and effect minimalism.
React + TanStack Query
Server state with TanStack Query: caching, mutations, optimistic updates.