Tables, Filters, and URL State
Use tables, filters, and url state with the current published Askr packages.
@askrjs/askr0.0.59
How to use tables, filters, and url state
Store shareable sort, filter, and page state in the URL; derive the query input from that state and render rows with stable keys.
- Import the published @askrjs/askr entrypoint.
- Keep tables, filters, and url state configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { currentRoute, updateRouteQuery } from '@askrjs/askr/router';
const route = currentRoute();
const filters = () => ({
status: route.query.status ?? 'active',
page: Number(route.query.page ?? 1),
});
function setStatus(status: string) {
updateRouteQuery({ status, page: 1 });
}Goal and architecture
There's no headless table primitive with built-in sort/filter/paginate logic in this framework — `@askrjs/ui`'s `Table`, `TableHead`, `TableBody`, `TableRow`, `TableHeaderCell`, and `TableCell` are presentational, styled components, not a data grid. Filter and sort state for a real table screen belongs in the URL, driven through the router's `updateRouteQuery()` and read back with the route's search params, with a `createQuery` refetching whenever that state changes.
Implementation
Call `updateRouteQuery({ sort: 'name', page: 2 })` to push new query params, or pass an updater function `(searchParams) => { ... }` from `RouteQueryUpdater` when you need to read the existing params before changing them; `UpdateRouteQueryOptions` controls whether the change pushes a new history entry or replaces the current one. Read the current filter/sort state back from the route's search object and pass it as part of your `createQuery` key, so changing a filter naturally produces a new cache entry and refetch instead of you managing a separate `useEffect`-style dependency list. Render the results into `Table`/`TableRow`/`TableCell` — they're just markup, so pagination and empty-state handling are your component's job, not the table's.
Failure states
An invalid or out-of-range filter value in the URL — a `page` beyond the last page, an unrecognized `sort` key — should clamp or fall back to a sane default rather than rendering an empty table with no explanation, since users routinely land on these URLs from bookmarks or shared links. A query that errors for a specific filter combination needs its own message; a bare `data.error` check without inspecting what actually failed will make every failure look identical to the user. Repeated keys in the query string become `string[]` on the server side (via `ctx.bind()`) — if you're also reading query params server-side for the same route, make sure both sides agree on that shape.
Verification
Change a filter, then use the browser back button, and confirm the table reflects the previous filter state rather than the current one — this is the entire point of putting the state in the URL instead of a component. Load the URL fresh with filter params already present (not by clicking through the UI) and confirm the table renders correctly on first paint, not just after a user interaction triggers a refetch. Finally check that two different filter combinations produce two distinct query cache entries rather than one clobbering the other.