@octanejs/nuqs
v0.1.38
Published
nuqs for the octane renderer — the nuqs 2.9.1 source ported onto octane's hooks: type-safe search params state (useQueryState/useQueryStates), the framework-agnostic parsers/serializer/loader reused verbatim, plus the React, custom and testing adapters.
Keywords
Readme
@octanejs/nuqs
nuqs for the octane UI framework — type-safe search-params state as octane hooks.
nuqs cleanly separates a framework-agnostic core (the parseAs* parsers,
createParser, createSerializer, createLoader, createStandardSchemaV1, and
the throttle/debounce update queues) from a small React binding
(useQueryState, useQueryStates, and the adapter context). This package
vendors the core verbatim from nuqs 2.9.1 and reimplements only the binding on
octane's hooks — keeping upstream's implementation shape (useSyncExternalStore
over location.search, render-time URL reconciliation, the shared throttle
queue), so re-render and URL-sync behavior match nuqs on React. The public
surface matches nuqs 1:1 — existing nuqs code works by changing the import.
// before
import { useQueryState, parseAsInteger } from 'nuqs';
// after
import { useQueryState, parseAsInteger } from '@octanejs/nuqs';
function Counter() @{
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0));
<button onClick={() => setCount((c) => (c ?? 0) + 1)}>count is {count as string}</button>
}Wrap your app in an adapter once, at the root:
import { NuqsAdapter } from '@octanejs/nuqs/adapters/react';
function App() @{
<NuqsAdapter>
<Counter />
</NuqsAdapter>
}Entry points
| import | what you get | notes |
| --- | --- | --- |
| @octanejs/nuqs | useQueryState, useQueryStates, all parseAs* parsers, createParser, createSerializer, createLoader, createStandardSchemaV1 | core vendored verbatim + the octane-bound hooks |
| @octanejs/nuqs/server | createLoader, createSerializer, parsers, createStandardSchemaV1 | react-free, safe to import from server-only modules |
| @octanejs/nuqs/adapters/react | NuqsAdapter, enableHistorySync | the standalone (router-less) adapter, ported to octane |
| @octanejs/nuqs/adapters/custom | unstable_createAdapterProvider, renderQueryString + adapter types | build an adapter for your own router |
| @octanejs/nuqs/adapters/testing | NuqsTestingAdapter, withNuqsTestingAdapter | in-memory adapter for tests |
| @octanejs/nuqs/testing | isParserBijective, testParseThenSerialize, testSerializeThenParse | parser test helpers (framework-agnostic) |
| @octanejs/nuqs/debug | side-effect import | opt debug logging into the bundle (localStorage.debug = 'nuqs') |
How it works
octane keys hooks by a compiler-injected per-call-site Symbol and transforms
raw-source octane packages automatically, so the ported hooks are written as
ordinary use* calls (no manual slot bookkeeping) — useQueryState('a') and
useQueryState('b') in one component stay independent, exactly like distinct
call sites in React.
useQueryStates is a line-for-line port of nuqs's implementation: it reads the
URL through the active adapter's useSyncExternalStore, reconciles the parsed
values into useState both during render and from an effect backstop, and
writes updates through nuqs's shared throttle/debounce queue. That means the same
observable behavior as nuqs on React, including:
- Read the default, write the URL. A missing key resolves to the parser's default without polluting the URL; the key is written only once the value diverges.
clearOnDefault. Setting a value back to its default removes the key from the URL (opt out per-call, per-parser, or per-adapter).- Cross-hook sync. Two components bound to the same key update together, and
external
popstate/history changes reconcile into state. - Throttled updates. Rapid setter calls coalesce through the shared queue and
return a
Promise<URLSearchParams>that resolves once the URL has committed.
Divergences from nuqs
- Router adapters for other React routers are not shipped:
nuqs/adapters/next,/adapters/remix,/adapters/react-router, and/adapters/tanstack-routereach bind a React router that would need its own octane port. Use/adapters/react, or/adapters/customto wire your router. createSearchParamsCacheis not ported. It is built on React Server Components'React.cache(), which octane does not implement (no Server Components). UsecreateLoaderfor request-scoped parsing.TransitionStartFunctionis declared locally, so the package carries no@types/reactdependency.
Status
Alpha, like octane itself. See status.json and the generated
bindings status table for the verified surface,
divergences, and last evidence check.
