[ PROMPT_NODE_25269 ]
Generic Components
[ SKILL_DOCUMENTATION ]
# Generic Component Patterns
Generic components provide type safety while maintaining reusability. TypeScript infers generic type parameters from prop values — no manual type annotations needed at call site.
## Generic Table Component
Full-featured table with typed columns, custom rendering, sorting.
```typescript
type Column = {
key: keyof T;
header: string;
render?: (value: T[keyof T], item: T) => React.ReactNode;
sortable?: boolean;
};
type TableProps = {
data: T[];
columns: Column[];
keyExtractor: (item: T) => string | number;
onSort?: (key: keyof T, direction: 'asc' | 'desc') => void;
className?: string;
};
function Table({
data,
columns,
keyExtractor,
onSort,
className,
}: TableProps) {
const [sortKey, setSortKey] = React.useState(null);
const [sortDir, setSortDir] = React.useState('asc');
const handleSort = (key: keyof T) => {
const newDir = sortKey === key && sortDir === 'asc' ? 'desc' : 'asc';
setSortKey(key);
setSortDir(newDir);
onSort?.(key, newDir);
};
return (
);
}
```
Usage:
```typescript
type User = {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
};
function UserTable({ users }: { users: User[] }) {
const [sortedUsers, setSortedUsers] = React.useState(users);
const handleSort = (key: keyof User, direction: 'asc' | 'desc') => {
const sorted = [...users].sort((a, b) => {
if (a[key] b[key]) return direction === 'asc' ? 1 : -1;
return 0;
});
setSortedUsers(sorted);
};
return (
{email},
},
{
key: 'role',
header: 'Role',
render: (role) => (
{role}
),
},
{
key: 'createdAt',
header: 'Created',
render: (date) => new Date(date).toLocaleDateString(),
sortable: true,
},
]}
keyExtractor={(user) => user.id}
onSort={handleSort}
/>
);
}
```
## Generic Select/Dropdown
Type-safe select with custom option rendering, searching.
```typescript
type SelectProps = {
options: T[];
value: T | null;
onChange: (value: T) => void;
getLabel: (option: T) => string;
getValue: (option: T) => string | number;
placeholder?: string;
disabled?: boolean;
searchable?: boolean;
};
function Select({
options,
value,
onChange,
getLabel,
getValue,
placeholder = 'Select...',
disabled = false,
searchable = false,
}: SelectProps) {
const [search, setSearch] = React.useState('');
const [isOpen, setIsOpen] = React.useState(false);
const filtered = searchable
? options.filter((opt) =>
getLabel(opt).toLowerCase().includes(search.toLowerCase())
)
: options;
const handleSelect = (option: T) => {
onChange(option);
setIsOpen(false);
setSearch('');
};
return (
| {col.sortable ? ( ) : ( col.header )} | ))}
|---|
| {col.render ? col.render(item[col.key], item) : String(item[col.key])} | ))}