> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Table Best Practices

> This guide provides best practices for implementing tables in the Encore OS platform using the @/platform/table-v2 framework.

**Version:** 1.0.0\
**Last Updated:** 2026-01-17\
**Status:** ✅ Active

***

## Overview

This guide provides best practices for implementing tables in the Encore OS platform using the `@/platform/table-v2` framework.

***

## Quick Decision Guide

### Which Component to Use?

| Scenario                                 | Component                      | Why                             |
| ---------------------------------------- | ------------------------------ | ------------------------------- |
| Simple list, \< 100 rows                 | `DataTable`                    | Easy setup, backward compatible |
| Moderate data, pagination needed         | `DataTable` with pagination    | Standard approach               |
| 500+ rows, all in memory                 | `VirtualizedTable`             | Prevents DOM bloat              |
| Server-side pagination                   | `DataTable` + `useServerTable` | Efficient data fetching         |
| Complex features (grouping, inline edit) | TanStack native + hooks        | Full control                    |

### When to Use Virtualization

**Use virtualization when:**

* Row count exceeds 500+
* Users frequently load full datasets
* Performance is degrading on scroll

**Skip virtualization when:**

* Row count is consistently \< 100
* Server-side pagination is used
* Mobile is the primary target (pagination is more user-friendly)

***

## Feature Selection Guide

### Export (useTableExport)

**Add export when:**

* Users need to share data externally (spreadsheets, reports)
* Compliance/audit requirements exist
* Data analysis workflows are common

**Implementation:**

```typescript theme={null}
import { useTableExport } from '@/platform/table-v2';

const { exportToCsv, exportToExcel, isExporting } = useTableExport({
  defaultFilename: 'accounts-export',
  alwaysExcludeColumns: ['actions'],  // Never export action buttons
});

// Add button to toolbar
<Button onClick={() => exportToCsv(table)} disabled={isExporting}>
  <Download className="h-4 w-4 mr-2" />
  Export CSV
</Button>
```

**Best Practices:**

* Always exclude `actions` column from exports
* Use descriptive filenames with date context
* Show loading state for large exports
* Consider Excel for formatted data (headers, number formatting)

### Column Pinning (useColumnPinning)

**Pin columns when:**

* Tables have many columns requiring horizontal scroll
* Key identifiers (ID, name) should always be visible
* Action buttons should be accessible without scrolling

**Implementation:**

```typescript theme={null}
import { useColumnPinning } from '@/platform/table-v2';

const { columnPinning, setColumnPinning } = useColumnPinning({
  persistenceKey: 'bills-table',
  initialPinning: { left: ['id'], right: ['actions'] },
});
```

**Best Practices:**

* Pin ID/identifier columns to the left
* Pin action columns to the right
* Limit pinned columns to 2-3 maximum
* Use persistence key for user preference retention

### Row Grouping (useRowGrouping)

**Use grouping when:**

* Data has natural categories (status, type, period)
* Aggregate values add insight (totals, counts, averages)
* Users need to focus on subsets of data

**Implementation:**

```typescript theme={null}
import { useRowGrouping } from '@/platform/table-v2';

const { groupedData, toggleGroup } = useRowGrouping({
  data: entries,
  groupByKey: 'period',
  aggregates: [
    { column: 'debit', type: 'sum', label: 'Total Debits' },
    { column: 'credit', type: 'sum', label: 'Total Credits' },
  ],
});
```

**Best Practices:**

* Limit grouping to 1-2 levels
* Show aggregates that provide actionable insights
* Default to collapsed for large datasets
* Persist expanded state in localStorage

### Inline Editing (useInlineEdit)

**Use inline editing when:**

* Quick edits are common workflow
* Context switching (modals) slows users down
* Fields are simple (text, number, select)

**Avoid inline editing when:**

* Complex validation is required
* Changes have significant side effects
* Audit trails need confirmation dialogs

**Implementation:**

```typescript theme={null}
import { useInlineEdit } from '@/platform/table-v2';

const { editingCell, startEdit, commitEdit, cancelEdit } = useInlineEdit({
  onSave: async (rowId, columnId, value) => {
    await updateRecord(rowId, { [columnId]: value });
  },
});
```

***

## Performance Best Practices

### 1. Always Use Pagination for Server Data

```typescript theme={null}
// ✅ Good - paginate on server
const { data } = await supabase
  .from('records')
  .select('*', { count: 'exact' })
  .range(offset, offset + pageSize - 1);

// ❌ Bad - fetch all, paginate client-side
const { data } = await supabase.from('records').select('*');
const pageData = data.slice(offset, offset + pageSize);
```

### 2. Memoize Column Definitions

```typescript theme={null}
// ✅ Good - memoized columns
const columns = useMemo<ColumnDef<Account>[]>(() => [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'balance', header: 'Balance' },
], []);

// ❌ Bad - recreated on every render
const columns: ColumnDef<Account>[] = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'balance', header: 'Balance' },
];
```

### 3. Use Skeleton Loaders

```typescript theme={null}
// ✅ Good - skeleton while loading
if (isLoading) {
  return <TableSkeleton rows={10} columns={5} />;
}

// ❌ Bad - empty or null while loading
if (isLoading) return null;
```

### 4. Optimize Render Functions

```typescript theme={null}
// ✅ Good - simple render function
render: (value) => formatCurrency(value)

// ❌ Bad - complex component in render
render: (value, row) => (
  <ComplexComponent 
    value={value} 
    row={row} 
    onAction={handleAction}
    options={options}
  />
)
```

***

## Mobile Considerations

### Responsive Table Patterns

1. **Horizontal scroll** (default)
   * Works for most tables
   * Add visual indicator for scrollable content

2. **Column hiding**
   * Hide non-essential columns on mobile
   * Use column visibility toggle

3. **Card layout**
   * For data-dense tables
   * Switch to vertical card layout on mobile

### Touch Targets

```typescript theme={null}
// ✅ Good - adequate touch target
<Button className="h-10 w-10 min-h-[44px] min-w-[44px]">
  <Edit className="h-4 w-4" />
</Button>

// ❌ Bad - too small for touch
<Button className="h-6 w-6">
  <Edit className="h-3 w-3" />
</Button>
```

***

## Common Patterns

### Filter + Table Pattern

```typescript theme={null}
function DataTableWithFilters() {
  const [filters, setFilters] = useState<Filters>({});
  const { data, isLoading } = useQuery({
    queryKey: ['data', filters],
    queryFn: () => fetchData(filters),
  });

  return (
    <div className="space-y-4">
      <TableFilters filters={filters} onChange={setFilters} />
      <DataTable data={data} columns={columns} isLoading={isLoading} />
    </div>
  );
}
```

### Bulk Actions Pattern

```typescript theme={null}
function TableWithBulkActions() {
  const [rowSelection, setRowSelection] = useState({});
  const selectedCount = Object.keys(rowSelection).length;

  return (
    <>
      {selectedCount > 0 && (
        <BulkActionBar 
          count={selectedCount}
          onDelete={() => handleBulkDelete(Object.keys(rowSelection))}
          onClear={() => setRowSelection({})}
        />
      )}
      <DataTable 
        selection={{ enabled: true, rowSelection, onRowSelectionChange: setRowSelection }}
      />
    </>
  );
}
```

### Empty State Pattern

```typescript theme={null}
<DataTable
  data={data}
  columns={columns}
  emptyState={{
    icon: <FileText className="h-12 w-12 text-muted-foreground" />,
    title: 'No invoices yet',
    description: 'Create your first invoice to get started.',
    action: (
      <Button onClick={handleCreate}>
        <Plus className="h-4 w-4 mr-2" />
        Create Invoice
      </Button>
    ),
  }}
/>
```

***

## Anti-Patterns to Avoid

### ❌ Fetching All Data Client-Side

```typescript theme={null}
// Bad - loads everything into memory
const { data } = useQuery({
  queryKey: ['all-records'],
  queryFn: () => supabase.from('records').select('*'),
});
```

### ❌ Recreating Table Instance

```typescript theme={null}
// Bad - new instance on every render
function MyTable({ data }) {
  const table = useReactTable({ data, columns, ... });
  return <BaseTable table={table} />;
}
```

### ❌ Complex Logic in Column Accessors

```typescript theme={null}
// Bad - expensive computation in accessor
{
  accessorFn: (row) => {
    const result = expensiveCalculation(row);
    return formatComplexValue(result);
  },
}
```

### ❌ Missing Loading States

```typescript theme={null}
// Bad - no feedback during load
if (!data) return <DataTable data={[]} />;
```

***

## References

* table-v2 README
* TanStack Table Migration Status
* [TanStack Table Documentation](https://tanstack.com/table/latest)
