TypeScript with React
Type React components, hooks, events, and context for safe, refactor-friendly UI code.
Typing Component Props
Define props with interfaces or type aliases: `interface ButtonProps { label: string; onClick: () => void; disabled?: boolean }`. Function components type props as the first generic parameter: `function Button({ label }: ButtonProps)`.
Extend native element props with `ComponentPropsWithoutRef<"button">` and intersect custom props. Use `React.PropsWithChildren` or explicit `children?: ReactNode` for components accepting children.
- Interface for props contract
- Extend native element props with utility types
- Explicit children typing
interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
variant?: 'primary' | 'secondary';
}Events and Refs
DOM events use specific types: `React.ChangeEvent<HTMLInputElement>`, `React.MouseEvent<HTMLButtonElement>`. Generic parameter matches the element type for correct target typing.
Refs use `useRef<HTMLInputElement>(null)` and check null before access. `forwardRef` components type the ref parameter with `React.forwardRef<HTMLInputElement, Props>`.
- Specific event types per element
- Ref generics with null checks
- forwardRef for ref-forwarding components
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};Hooks Typing
useState infers from initial value — provide explicit generic when initial value is null: `useState<User | null>(null)`. useReducer types state and action union. useContext requires typed context created with `createContext<T | null>(null)` and null checks at consumption.
Custom hooks return typed tuples or objects — export return types for consumers. Generic custom hooks (`function useLocalStorage<T>`) preserve value types through serialization boundaries when combined with runtime validation.
- Explicit useState generic for nullable init
- Typed context with null guard
- Generic custom hooks for reusable logic
function useUser(id: string) {
const [user, setUser] = useState<User | null>(null);
// ...
return { user, loading } as const;
}Server and Client Components
Next.js Server Components are async functions returning JSX — type props as plain interfaces without hooks. Client Components need `"use client"` and follow standard React typing.
Shared types live in non-component modules importable from both server and client. Never import server-only modules into client components — TypeScript project references and eslint boundaries help enforce separation.
- Server Components: async, no hooks
- Shared types in neutral modules
- Enforce server/client import boundaries
Third-Party Components and Generics
Libraries like Radix, MUI, and TanStack Table ship excellent types — leverage them rather than casting. When wrapping third-party components, re-export narrowed prop types for your design system.
Polymorphic components (`as` prop) use generics and conditional types — advanced pattern for design systems. Start with fixed element types and add polymorphism only when multiple HTML elements are required.
- Leverage library shipped types
- Wrap and narrow props for design systems
- Polymorphic components are advanced — defer until needed