@cogs/nuqs
v0.2.0
Published
Reusable nuqs (URL search-param state) patterns — table pagination/filter hook factories, page-index conversion, sort/filter parser builders, and typed search-param API helpers.
Readme
@cogs/nuqs
Reusable nuqs (URL search-param state) patterns extracted from environment-manager-ui's ~20 near-identical table pagination/filter hooks: page-index conversion, sort/filter parser builders, typed search-param API helpers, and a createTableUrlState factory that collapses the whole table-filtering template into one call. Re-exports nuqs's own primitives too, so consumers can depend on @cogs/nuqs alone.
Features
createTableUrlState(config)— the core abstraction. Generalizes envmgr-ui'suse*Filters.tstemplate (page/per_page, sort, search, faceted filters) into a factory that always resetspageback to0on any filter/sort/search change, accepts an optionalstartTransitionuniformly, and exposes both a combinedresetFiltersAndSearchand splitresetFilters/resetSearch.pageIndexParser/pageIndexParserStrict— 0-indexed internal state, 1-indexed URL (?page=1→page 0internally), for human-friendly pagination links.buildFilterParsers— maps a generic{ id, options? }[]field list to nuqs parsers (faceted → comma-array, text → string).getColumnsSortParser— encodes/decodes multi-column{ id, desc }[]sort state ascol-dir,col-dirquery tokens, with optional column-id validation against a sample row.createSearchParamsAPI/createSerializerForParams— typed, server-safeURLSearchParams↔ object helpers.useSyncedQueryParamField— keeps a single URL query param in sync with a form field'sonChange.parseServerSeed— server-side half of "seed a React Hook Form default from the URL, then sync back client-side."@cogs/nuqs/testing— a statefuluseQueryState/useQueryStatestest double that correctly round-trips index-conversion parsers (likepageIndexParserStrict) and respectsclearOnDefault, unlike a naive no-op mock.- Re-exports
nuqs's client hooks and parser builders (useQueryState,parseAsInteger, etc.), so apps depend on one package for both.
Installation
pnpm add @cogs/nuqsnuqs is a direct dependency (tracks its own latest major independently of any one consuming app's pinned version — see Compatibility below). react is an optional peer dependency, required only if you use the hook-based exports (createTableUrlState, useSyncedQueryParamField, @cogs/nuqs/testing).
Usage
Table pagination/filter/sort/search state
// dnsHealthTableUrlState.ts
import { buildFilterParsers, createTableUrlState, type FilterParserBuilder } from '@cogs/nuqs'
const filters = buildFilterParsers(
[
{ id: 'status', options: ['healthy', 'degraded', 'down'] },
{ id: 'checkType', options: ['a', 'cname', 'mx'] },
],
{ shallow: true },
) as Record<'status' | 'checkType', FilterParserBuilder<string[]>>
export const { useTableUrlState: useDnsHealthFilters } = createTableUrlState({ filters })// DnsHealthTable.tsx
import { useTransition } from 'react'
import { useDnsHealthFilters } from './dnsHealthTableUrlState'
export function DnsHealthTable() {
const [, startTransition] = useTransition()
const {
pagination,
filters,
sorting,
searchQuery,
isAnyFilterActive,
setFilters,
setSorting,
setSearchQuery,
resetFiltersAndSearch,
} = useDnsHealthFilters({ startTransition })
const table = useReactTable({
data,
columns,
state: {
pagination: { pageIndex: pagination.page, pageSize: pagination.per_page },
sorting,
},
// ...
})
// setFilters/setSorting/setSearchQuery all reset `page` back to 0 automatically.
}Page-index conversion
import { pageIndexParserStrict } from '@cogs/nuqs'
pageIndexParserStrict.parse('1') // 0 (0-indexed internally)
pageIndexParserStrict.serialize(0) // '1' (1-indexed in the URL)Multi-column sort
import { getColumnsSortParser } from '@cogs/nuqs'
const sortParser = getColumnsSortParser<{ name: string; createdAt: string }>()
sortParser.parse('name-asc,createdAt-desc')
// → [{ id: 'name', desc: false }, { id: 'createdAt', desc: true }]Testing
// jest/vitest module mapper — point `nuqs`, `nuqs/server`, and
// `nuqs/adapters/next/app` all at this one module:
// moduleNameMapper: {
// '^nuqs$': '@cogs/nuqs/testing',
// '^nuqs/server$': '@cogs/nuqs/testing',
// '^nuqs/adapters/next/app$': '@cogs/nuqs/testing',
// }For hook-level tests of code built with createTableUrlState directly (not through the app's mocked nuqs), prefer nuqs's own nuqs/adapters/testing (NuqsTestingAdapter / withNuqsTestingAdapter, with hasMemory: true) as a renderHook wrapper — that's what this package's own test suite uses to verify createTableUrlState against real nuqs behavior, not a hand-mimicked stub.
Compatibility
This package depends on nuqs@latest and tracks upstream independently — it is not pinned to any particular consuming app's nuqs version. If your app is on an older nuqs major, verify createParser/withOptions/Options shapes still match before adopting.
Development
pnpm --filter @cogs/nuqs build
pnpm --filter @cogs/nuqs typecheck
pnpm --filter @cogs/nuqs lint
pnpm --filter @cogs/nuqs testcreateTableUrlState'sfiltersconfig expects an all-faceted (array-valued) parser record — pass only faceted fields frombuildFilterParsers. A standalone text filter should be wired up on its ownuseQueryState, outside the factory.- No module in this package has import-time side effects — every export is safe to import at module scope without triggering unrelated app initialization.
