āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/47ng/nuqs/utilities ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
To populate <Link>{:tsx} components with state values, you can use the createSerializer{:ts}
helper.
Pass it an object describing your search params, and it will give you a function to call with values, that generates a query string serialized as the hooks would do.
Example:
// [!code word:createSerializer]
import {
createSerializer,
parseAsInteger,
parseAsIsoDateTime,
parseAsString,
parseAsStringLiteral
} from 'nuqs/server' // can also be imported from 'nuqs' in client code
const searchParams = {
search: parseAsString,
limit: parseAsInteger,
from: parseAsIsoDateTime,
to: parseAsIsoDateTime,
sortBy: parseAsStringLiteral(['asc', 'desc'])
}
// Create a serializer function by passing the description of the search params to accept
const serialize = createSerializer(searchParams)
// Then later, pass it some values (a subset) and render them to a query string
serialize({
search: 'foo bar',
limit: 10,
from: new Date('2024-01-01'),
// here, we omit `to`, which won't be added
sortBy: null // null values are also not rendered
})
// ?search=foo+bar&limit=10&from=2024-01-01T00:00:00.000Z
The returned serialize{:ts} function can take a base parameter over which to
append/amend the search params:
serialize('/path?baz=qux', { foo: 'bar' }) // /path?baz=qux&foo=bar
const search = new URLSearchParams('?baz=qux')
serialize(search, { foo: 'bar' }) // ?baz=qux&foo=bar
const url = new URL('https://example.com/path?baz=qux')
serialize(url, { foo: 'bar' }) // https://example.com/path?baz=qux&foo=bar
// Passing null removes existing values
serialize('?remove=me', { foo: 'bar', remove: null }) // ?foo=bar
Just like with useQueryStates{:ts}, you can
define a urlKeys{:ts}
object to map the variable names defined by the parsers to shorter keys in the URL:
const serialize = createSerializer(
{
// 1. Use variable names that make sense for your domain/business logic
latitude: parseAsFloat,
longitude: parseAsFloat,
zoomLevel: parseAsInteger
},
{
// 2. Remap them to shorter keys in the URL
urlKeys: {
latitude: 'lat',
longitude: 'lng',
zoomLevel: 'z'
}
}
)
// 3. Use your variable names when calling the serializer,
// and the shorter keys will be rendered in the URL:
serialize({
latitude: 45.18,
longitude: 5.72,
zoomLevel: 12
})
// ?lat=45.18&lng=5.72&z=12
Just like in the <NuqsAdapter>{:tsx},
you can specify a processUrlSearchParams{:ts} function
to modify the search params before they are serialized.
For example, it can be useful for consistent canonical URLs (for SEO), by sorting search param keys alphabetically:
// [!code word:processUrlSearchParams]
const serialize = createSerializer(
{
a: parseAsInteger,
z: parseAsInteger
},
{
processUrlSearchParams: (search) => {
// Note: you can modify search in-place,
// or return a copy, as you wish.
search.sort()
return search
}
}
)
serialize('?foo=bar', {
a: 1,
z: 1
})
// ?a=1&foo=bar&z=1
// merged, then sorted
To access the underlying type returned by a parser, you can use the
inferParserType{:ts} type helper:
import { parseAsInteger, type inferParserType } from 'nuqs' // or 'nuqs/server'
const intNullable = parseAsInteger
const intNonNull = parseAsInteger.withDefault(0)
inferParserType<typeof intNullable> // number | null
inferParserType<typeof intNonNull> // number
For an object describing parsers (that you'd pass to createLoader{:ts}
or to useQueryStates{:ts}), inferParserType{:ts} will
return the type of the object with the parsers replaced by their inferred types:
import {
parseAsBoolean,
parseAsInteger,
type inferParserType
} from 'nuqs' // or 'nuqs/server'
const parsers = {
a: parseAsInteger,
b: parseAsBoolean.withDefault(false)
}
inferParserType<typeof parsers>
// { a: number | null, b: boolean }
Search param definitions can be turned into a Standard Schema for validating external sources and passing on type inference to other tools.
// [!code word:validateSearchParams]
import {
createStandardSchemaV1,
parseAsInteger,
parseAsString,
} from 'nuqs' // or 'nuqs/server'
// 1. Define your search params as usual
export const searchParams = {
searchTerm: parseAsString.withDefault(''),
maxResults: parseAsInteger.withDefault(10)
}
// 2. Then create a Standard Schema compatible validator
export const validateSearchParams = createStandardSchemaV1(searchParams)
// 3. Use it with other tools, like tRPC:
router({
search: publicProcedure.input(validateSearchParams).query(...)
})
You can pass the standard schema validator to
TanStack Router's validateSearch{:ts} for type-safe
linking to nuqs URL state, but in order to keep those
values optional (as nuqs uses different defaults strategies
than TSR), you need to mark the output as Partial{:ts},
using the partialOutput{:ts} option:
// [!code word:partialOutput]
import { createStandardSchemaV1 } from 'nuqs'
const validateSearch = createStandardSchemaV1(searchParams, {
partialOutput: true
})
export const Route = createFileRoute('/search')({
validateSearch
})
<Callout title="Note" type="warn">
TanStack Router support is still experimental and comes with caveats,
see the [adapter documentation](/docs/adapters#tanstack-router) for more
information about what is supported.
</Callout>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā