Skip to content

Custom table layouts

Pass children to <AppTable> to render a different layout. Use regions when you only need to replace part of the table.

This example expects a name column. It uses one snapshot for both the result and request status:

React

import { AppTable } from '@/components/app-table';
import { useTableController, useTableSnapshot } from '@inertiax/react-table';
function NameList() {
const snapshot = useTableSnapshot();
const controller = useTableController();
const busy = snapshot.status.type === 'pending' || snapshot.status.type === 'scheduled';
if (snapshot.outcome === 'problem') {
return (
<div role="alert" aria-busy={busy}>
<p>This table link needs updating.</p>
{snapshot.status.type === 'error' ? (
<button type="button" onClick={() => controller.retry()}>Retry</button>
) : snapshot.canReset ? (
<button type="button" disabled={busy} onClick={() => controller.resetProblem()}>
Reset table settings
</button>
) : <p>Open a new table link to continue.</p>}
</div>
);
}
const { component } = snapshot.envelope;
return (
<div aria-busy={busy}>
{busy && <p role="status">Loading names…</p>}
{(snapshot.status.type === 'error' || snapshot.status.type === 'externalError') && (
<p role="alert">
Could not update the list.{' '}
{snapshot.status.type === 'error' && (
<button type="button" onClick={() => controller.retry()}>Retry</button>
)}
</p>
)}
{component.data.rows.length === 0 ? <p>No matching names.</p> : (
<ul>
{component.data.rows.map((row) => (
<li key={String(row[component.data.rowKey]?.value)}>
{String(row.name?.value ?? '')}
</li>
))}
</ul>
)}
</div>
);
}
<AppTable prop="users"><NameList /></AppTable>;

Replacement children own the whole UI. This list displays the current page; add the table controls for search, filters, sorting, and pagination, or build your own with the controller, for example for selection. Previous rows remain visible during a reload or a failed request. An invalid-link problem has no successful result, so that branch provides reset or retry instead.

useTableSnapshot() works for both outcomes. Hooks that require a result, such as useTableEnvelope() and useTableState(), must only mount after a successful outcome.

Add search, filters, sorting, and pagination

Section titled “Add search, filters, sorting, and pagination”

Place TableSearch, TableFilters, TableSort, and TablePagination around your own layout to add search, filters, sorting, and pagination. This example shows orders as cards and expects reference and customer_name columns:

React

import { AppTable } from '@/components/app-table';
import {
TableFilters,
TablePagination,
TableSearch,
TableSort,
useTableSnapshot,
} from '@inertiax/react-table';
function OrderCards() {
const snapshot = useTableSnapshot();
if (snapshot.outcome === 'problem') return <p role="alert">This table link needs updating.</p>;
const { data } = snapshot.envelope.component;
return (
<ul className="grid gap-4 sm:grid-cols-2">
{data.rows.map((row) => (
<li key={String(row[data.rowKey]?.value)}>
<strong>{String(row.reference?.value ?? '')}</strong>
<p>{String(row.customer_name?.value ?? '')}</p>
</li>
))}
</ul>
);
}
<AppTable prop="orders">
<div className="flex flex-wrap gap-2">
<TableSearch />
<TableSort />
<TableFilters />
</div>
<OrderCards />
<TablePagination />
</AppTable>;

Each control renders nothing when the table doesn’t offer its feature, so the same controls work with any table. They also render nothing for a problem outcome, so they can stay outside your layout’s success branch. They use the table’s texts and theme, and TableSort sets one sort key at a time. This example leaves out the loading, empty, error, and reset handling a custom layout owns; add it as in the name list. See Layout controls for each component.

Use customization.texts.problem to change the wording. For a custom layout, use problemState:

React

<AppTable
prop="orders"
customization={{
regions: {
problemState: ({ canReset, controller, status }) => (
<div role="alert">
<p>This table link needs updating.</p>
{status.type === 'error' ? (
<button type="button" onClick={() => controller.retry()}>Retry</button>
) : canReset ? (
<button
type="button"
disabled={status.type === 'pending' || status.type === 'scheduled'}
onClick={() => controller.resetProblem()}
>
Reset table settings
</button>
) : <p>Open a new table link to continue.</p>}
</div>
),
},
}}
/>

The region receives componentId, problem, controller, status, and canReset. Unlike the loading, empty and request-error regions, it replaces the whole table and returns ordinary markup, not table rows. See Regions for the full contract.

View Markdown