Advanced React + TS Patterns
HardAdvanced React TypeScript patterns combine generics, conditional types, and mapped types to create type-safe APIs for complex component libraries. These patterns include generic list/table components, polymorphic `as` props, strictly typed context with no undefined checks, and form handling with inferred field types. Mastering these patterns is what separates senior frontend engineers from intermediate ones.
Interactive Visualization
Key Points
- Polymorphic `as` prop lets a component render as any HTML element or component
- Generic components maintain type relationships between data and render callbacks
- Strictly typed context factories eliminate undefined checks at usage sites
- Compound components use React context with TypeScript for type-safe slot patterns
- Forwarded refs require `React.forwardRef` with explicit generic type parameters
- Template literal types can generate prop types from string patterns
Code Examples
Polymorphic Components
type PolymorphicProps<E extends React.ElementType, P = object> = P & Omit<React.ComponentPropsWithoutRef<E>, keyof P | 'as'> & { as?: E } // Box component that can render as any element function Box<E extends React.ElementType = 'div'>({ as, children, ...rest }: PolymorphicProps<E, { children?: React.ReactNode }>) { const Component = as || 'div' return <Component {...rest}>{children}</Component> } // Usage <Box as="section" id="main">Content</Box> <Box as="a" href="/home">Link</Box> // <Box as="a" href={42} /> // Error: href must be string // With custom props type TextProps<E extends React.ElementType> = PolymorphicProps<E, { size?: 'sm' | 'md' | 'lg' weight?: 'normal' | 'bold' }> function Text<E extends React.ElementType = 'span'>({ as, size, weight, ...rest }: TextProps<E>) { const Component = as || 'span' return <Component {...rest} /> }
Polymorphic components accept an `as` prop that changes the rendered element. TypeScript ensures that the remaining props match the element type, preventing invalid prop combinations.
Strictly Typed Context
// Context factory — eliminates undefined checks at usage sites function createStrictContext<T>(displayName: string): [ React.Provider<T>, () => T, ] { const Context = React.createContext<T | undefined>(undefined) Context.displayName = displayName function useStrictContext(): T { const value = React.useContext(Context) if (value === undefined) { throw new Error( `use${displayName} must be used within a ${displayName}Provider` ) } return value } return [Context.Provider as React.Provider<T>, useStrictContext] } // Usage interface AuthContext { user: User logout: () => void } const [AuthProvider, useAuth] = createStrictContext<AuthContext>('Auth') // In component — no undefined check needed function Profile() { const { user, logout } = useAuth() // AuthContext, not AuthContext | undefined return <div>{user.name}</div> } interface User { name: string; email: string }
The strict context factory pattern creates a context and hook pair where the hook throws if used outside the provider. This eliminates the need for undefined checks at every usage site.
Generic List Component
// Generic list with type-safe item rendering interface ListProps<T> { items: T[] keyExtractor: (item: T) => string renderItem: (item: T, index: number) => React.ReactNode emptyMessage?: string } function List<T>({ items, keyExtractor, renderItem, emptyMessage = 'No items', }: ListProps<T>) { if (items.length === 0) { return <div className="empty">{emptyMessage}</div> } return ( <ul> {items.map((item, index) => ( <li key={keyExtractor(item)}>{renderItem(item, index)}</li> ))} </ul> ) } // Usage — T is inferred from items interface Todo { id: string text: string done: boolean } const todos: Todo[] = [] <List items={todos} keyExtractor={t => t.id} renderItem={(todo) => ( <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}> {todo.text} </span> )} />
Generic list components infer their type parameter from the items array. The renderItem callback then receives properly typed items, enabling type-safe rendering without manual type annotations.
Common Mistakes
- Making polymorphic components too complex — not all components need the `as` pattern
- Not using context factories, leading to `| undefined` checks everywhere
- Forgetting to handle the empty/default case in generic components
- Over-abstracting with generics when a simpler component with specific props would suffice
- Not testing polymorphic components with various element types to catch type issues
Interview Tips
- The polymorphic `as` pattern shows deep TypeScript + React knowledge
- Explain the strict context factory — it solves a real pain point in React + TS
- Generic components demonstrate both React architecture and TypeScript fluency
- Know when these patterns are worth the complexity vs simpler alternatives