# DataTable

> A fully featured table built from a list hook — columns, search, sorting and paging all inferred.

`<DataTable>` reads whatever a list hook exposes and lights up features accordingly. Hand it a query; it does the rest.

```tsx title="app/airports.tsx"
<DataTable query={useListAirports({ options: { pageSize: 20 } })} />
```

## Feature detection

The table **feature-detects** from the hook, so the same component drives both a REST `IListQuery` (search + sort + paging) and a `sync: { mode: "local" }` entity list — the latter simply renders no toolbar or pager.

| From the hook | Renders |
| --- | --- |
| `header` (`{ key, name }[]`) | column headers (else the first row's keys) |
| `data` | rows, each cell via `formatCell` or a column's `render` |
| `sorting` | clickable sortable headers with a direction indicator |
| `searching` | a debounced search box |
| `pagination` | a footer pager + record count |
| `isLoading` / empty / `error` | the matching state row |

## Customising columns

Customise without giving up the wiring — pass explicit `columns` (including an FK cell rendered as a link) and an `onRowClick`:

```tsx title="app/terminals.tsx"
<DataTable
  query={useListTerminals()}
  columns={[
    { key: "label", header: "Terminal" },
    { key: "airport", header: "Airport",
      render: (t) => <a href={`/airports/${t.airport}`}>{t.airport}</a> },
    { key: "gates", align: "right" },
  ]}
  onRowClick={(t) => navigate(`/terminals/${t.id}`)}
/>
```

## Headless

Go fully headless with the model + a render-prop escape hatch:

```tsx title="headless"
const model = useDataTable(query, { columns });
// { columns, rows, isLoading, isEmpty, error, search?, sort?, pagination? }

<DataTable query={query}>{(m) => /* your own markup from m */}</DataTable>
```

---

_Source: [https://reactapistack.com/docs/data-table](https://reactapistack.com/docs/data-table) · Full docs as one file: [llms-full.txt](https://reactapistack.com/llms-full.txt)_
