navree
Documentation

Data tables

Lists driven by list queries: filters, sort, search and paging in the URL, sorted and paged on the server, rendered by DataTable (React) or <NavreeDataTable> (Vue) on TanStack Table.

On this page

A list screen has the same parts in every app: a table of one page of records, sortable headings, a search box, filters and the page buttons, all kept in the URL so a reload, the back button and a shared link show the same list. With Navree the server does the sorting, filtering and paging (the list endpoint reads a list query), so the table only renders the page it gets and turns clicks into the next list query. @navree/next and @navree/nuxt ship that table on TanStack Table, styled with the theme.

The URL is the state

useListQuery(defaults) reads the list query from the current URL and writes changes back with router.replace, keeping the page's other parameters:

MemberWhat it does
queryThe ListQuery in the URL, over defaults.
setQuery(next)Replaces the list query (a value, or a function of the current one).
setFilter(field, value)Sets one filter, or removes it with undefined, and goes back to page 1.
setSearch(text)Sets the search (an empty string removes it) and goes back to page 1.
toggleSort(field, {multi})Ascending, descending, off; other fields are dropped unless multi.
setPage(n)Moves to page n (page 1 is left out of the URL).

Values equal to defaults stay out of the URL, so useListQuery({ per_page: 25 }) keeps ?per_page=25 from every link.

Text

In Next.js the hook uses next/navigation, so the component must sit under a <Suspense> boundary (Next.js requires one around useSearchParams in pages that are rendered ahead of time). In Nuxt it uses vue-router's route query, and its setters return the navigation's promise.

Next.js

web/src/app/(app)/deals/deals-table.tsx

DataTable props:

PropMeaning
columnsTanStack column definitions; meta takes NavreeColumnMeta.
data, metaThe page's rows and meta (current_page, per_page, total, last_page).
query, onQueryChangeThe current list query, and what to do with the next one (setQuery from useListQuery).
loading, loadingRowsPlaceholder rows instead of the data (5 by default).
emptyWhat an empty page shows (default: the empty label).
onRowClick(row)Makes rows clickable.
getRowId(row)Row keys (and data-row-id on each row).
labelsprevious, next, empty, loading, pagination, and range(from, to, total).
classNameClasses on the outer surface.

NavreeColumnMeta:

FieldMeaning
sortFieldThe list-query field the column sorts by; the heading becomes a button only when it is set.
alignstart (default), center or end (amounts and counts).
classNameClasses for the column's heading and cells, such as hidden md:table-cell to drop a column on phones.

A heading click sends sort and drops page and cursor; shift-click adds the column to the sort. Each heading carries aria-sort. The page buttons send page. The pagination bar shows "26–50 of 60" and hides when the list is empty. Pagination is exported on its own for card grids and boards.

Your own markup

useDataTable(options) is the same table without markup: TanStack Table's instance with manual sorting and paging, plus toggleColumnSort(header, multi), columnSort(header) and goToPage(n). Use it to render the table with your own shadcn/ui Table components.

Nuxt

web/app/pages/deals/index.vue

<NavreeDataTable> takes the same props as the React table (columns, data, meta, query, loading, loadingRows, labels, getRowId, plus clickable), emits update:query and row-click, and has an empty slot. Its markup carries shadcn-vue's data-slot attributes (table, table-header, table-row, table-head, table-body, table-cell), so it sits next to shadcn-vue components. useDataTable from @navree/nuxt/vue is the table without markup, for your own shadcn-vue Table.

Filters and facets

Filters are list-query filters, so a filter control is a call to setFilter:

TSX

When the endpoint reports facets in meta.facets, facetCounts(meta, "stage") from @navree/client gives [{value, count, label?}] for a filter's options, whether the API sends a map of counts or a list.

Right to left

The table uses logical properties (text-start, text-end, px-3), so in a page with dir="rtl" its columns, alignment and pagination mirror without extra classes. Pass labels from your translations, including range, to format the numbers for the locale:

TypeScript