- React
- Type-Safe React: Components, Generics, and Hooks
- Generic Components: Type-Safe Tables, Selects & Lists
Generic Components: Type-Safe Tables, Selects & Lists
Generic React components accept TypeScript type parameters `<T>`, enabling reusable UI primitives like Tables, Selects, and Virtual Lists to enforce full type safety on data items, callbacks, and custom cell renderers without losing type inference.
Key Learning Objectives
Implement reusable generic React components using `<T>` type parameters.
Enforce type inference on selection callbacks (e.g. `onSelect: (item: T) => void`).
Design type-safe generic Table column definitions with keyed accessors and cell formatters.
Apply generic constraints (`<T extends { id: string }>`) to enforce minimum data contracts.
The Interview Problem & Context
Build a fully type-safe generic `<Select>` component and `<DataTable>` component that infers item types automatically without requiring manual type casting.
1import React from 'react';23// 1. Generic Select Component4interface SelectProps<T> {5 items: T[];6 selectedItem: T | null;7 onSelect: (item: T) => void;8 getLabel: (item: T) => string;9 getKey: (item: T) => string | number;10}1112export function Select<T>({ items, selectedItem, onSelect, getLabel, getKey }: SelectProps<T>) {13 return (14 <ul role="listbox" className="select-list">15 {items.map((item) => {16 const isSelected = selectedItem ? getKey(selectedItem) === getKey(item) : false;17 return (18 <li19 key={getKey(item)}20 role="option"21 aria-selected={isSelected}22 onClick={() => onSelect(item)}23 className={isSelected ? 'selected' : ''}24 >25 {getLabel(item)}26 </li>27 );28 })}29 </ul>30 );31}3233// 2. Generic Column Definition for DataTable34export interface Column<T> {35 header: string;36 accessorKey?: keyof T;37 render?: (item: T) => React.ReactNode;38}3940interface DataTableProps<T extends { id: string | number }> {41 data: T[];42 columns: Column<T>[];43}4445export function DataTable<T extends { id: string | number }>({ data, columns }: DataTableProps<T>) {46 return (47 <table className="data-table">48 <thead>49 <tr>50 {columns.map((col, idx) => (51 <th key={idx}>{col.header}</th>52 ))}53 </tr>54 </thead>55 <tbody>56 {data.map((row) => (57 <tr key={row.id}>58 {columns.map((col, idx) => (59 <td key={idx}>60 {col.render61 ? col.render(row)62 : col.accessorKey63 ? String(row[col.accessorKey])64 : null}65 </td>66 ))}67 </tr>68 ))}69 </tbody>70 </table>71 );72}
Deep Architectural Explanation
How Generic React Components Work
When you write:
export function Select<T>({ items, onSelect }: SelectProps<T>) { ... }TypeScript uses argument type inference:
- When you pass
items={users}whereusersisUser[], TypeScript bindsT = User. onSelectis immediately inferred as(item: User) => void.- Inside your callback
onSelect={(u) => console.log(u.email)},uis auto-completed with fullUserproperties—zero manual type casting needed!
TSX Syntax Gotcha with Arrow Functions:
In .tsx files, arrow function generics can confuse the JSX parser:
// Syntax Error: JSX parser thinks <T> is an unclosed HTML tag!
const Select = <T>(props: SelectProps<T>) => ...
// Solution 1: Use function declaration
function Select<T>(props: SelectProps<T>) { ... }
// Solution 2: Add trailing comma or extends constraint
const Select = <T,>(props: SelectProps<T>) => ...
const Select = <T extends unknown>(props: SelectProps<T>) => ...Common Interview Traps & Pitfalls
Using `any[]` for reusable list or table data, throwing away type safety across the entire component.
Getting tripped up by `.tsx` syntax errors when writing arrow function generics `<T>`.
Over-constraining generic types unnecessarily, preventing primitive types (strings, numbers) from being used.
Senior Interviewer Follow-Up Probes
How do libraries like TanStack Table type deeply nested column accessor keys (e.g., `user.address.city`)?
How do you combine generic components with React.memo or forwardRef?
How can you enforce that at least one of `accessorKey` or `render` is provided in a column definition?
Frequently Asked Interview Questions
Why does const Component = <T> cause an error in .tsx files?
The TSX compiler interprets <T> as an opening JSX HTML tag. Adding a trailing comma (<T,>) or a constraint (<T extends unknown>) resolves the ambiguity.
How does type inference work with generic components?
TypeScript inspects the props passed to the component (like data or items) and automatically infers the generic type T. All associated callbacks (e.g., onSelect: (item: T) => void) inherit that inferred type.
What is a generic constraint in React component props?
Using extends (such as <T extends { id: string }>) to require that any data object passed into the component must contain specific properties (like an id for React keys).
Can you wrap a generic component in React.memo?
Yes, but React.memo erases generic type parameters unless wrapped in a custom type assertion or helper function casting memo back to generic component signatures.
When should a component be made generic?
Whenever a component operates on collections of items (tables, dropdowns, auto-completes, virtual lists, paginators) and passes individual items back to consumer callbacks or render props.
