typescriptintermediate

Sortable Data Table Component

Reusable data table with column sorting, type-safe accessors, and sort direction indicators.

typescript
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.