typescriptintermediate
Sortable Data Table Component
Reusable data table with column sorting, type-safe accessors, and sort direction indicators.
typescriptPress ⌘/Ctrl + Shift + C to copy
import { useState, useMemo, ReactNode } from 'react';
interface Column<T> {
key: keyof T;
header: string;
render?: (value: T[keyof T], row: T) => ReactNode;
sortable?: boolean;
}
interface DataTableProps<T> {
data: T[];
columns: Column<T>[];
}
export function DataTable<T extends Record<string, unknown>>({ data, columns }: DataTableProps<T>) {
const [sortKey, setSortKey] = useState<keyof T | null>(null);
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const sorted = useMemo(() => {
if (!sortKey) return data;
return [...data].sort((a, b) => {
const aVal = a[sortKey], bVal = b[sortKey];
const cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
return sortDir === 'asc' ? cmp : -cmp;
});
}, [data, sortKey, sortDir]);
const toggleSort = (key: keyof T) => {
if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
else { setSortKey(key); setSortDir('asc'); }
};
return (
<table className="w-full text-left">
<thead>
<tr>
{columns.map(col => (
<th
key={String(col.key)}
className={`p-2 ${col.sortable !== false ? 'cursor-pointer select-none' : ''}`}
onClick={() => col.sortable !== false && toggleSort(col.key)}
>
{col.header}
{sortKey === col.key && (sortDir === 'asc' ? ' ▲' : ' ▼')}
</th>
))}
</tr>
</thead>
<tbody>
{sorted.map((row, i) => (
<tr key={i} className="border-t border-white/10">
{columns.map(col => (
<td key={String(col.key)} className="p-2">
{col.render ? col.render(row[col.key], row) : String(row[col.key] ?? '')}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}Use Cases
- Admin dashboards
- Data browsing interfaces
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptintermediate
Error Boundary with Fallback UI
Class-based error boundary component that catches render errors and displays a customizable fallback UI.
Best for: Graceful error recovery
#error-boundary#error-handling
typescriptintermediate
Portal-Based Modal Component
Accessible modal component using React portals with focus trapping, Escape key close, and backdrop click.
Best for: Confirmation dialogs
#modal#portal
typescriptintermediate
Accordion Component
Accessible accordion component with keyboard navigation and animated expand/collapse.
Best for: FAQ sections
#accordion#a11y
typescriptintermediate
Portal Dropdown Component
Dropdown menu rendered via Portal to avoid overflow clipping, with click-outside dismiss.
Best for: Navigation menus
#portal#dropdown