nuqs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

nuqs Best Practices

nuqs 最佳实践

Type-safe URL query state management for React. Like
useState
, but stored in the URL.
面向React的类型安全URL查询状态管理工具,功能类似
useState
,但状态存储在URL中。

Setup (Required First)

配置(必须优先完成)

Wrap your app with the appropriate adapter:
tsx
// Next.js App Router - app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app'

export default function RootLayout({ children }) {
  return <NuqsAdapter>{children}</NuqsAdapter>
}

// Next.js Pages Router - pages/_app.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/pages'

// React SPA (Vite/CRA)
import { NuqsAdapter } from 'nuqs/adapters/react'

// Remix - app/root.tsx
import { NuqsAdapter } from 'nuqs/adapters/remix'

// React Router v6/v7
import { NuqsAdapter } from 'nuqs/adapters/react-router'
使用对应框架的适配器包裹你的应用:
tsx
// Next.js App Router - app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app'

export default function RootLayout({ children }) {
  return <NuqsAdapter>{children}</NuqsAdapter>
}

// Next.js Pages Router - pages/_app.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/pages'

// React SPA (Vite/CRA)
import { NuqsAdapter } from 'nuqs/adapters/react'

// Remix - app/root.tsx
import { NuqsAdapter } from 'nuqs/adapters/remix'

// React Router v6/v7
import { NuqsAdapter } from 'nuqs/adapters/react-router'

Global Options

全局配置

tsx
import { throttle } from 'nuqs'

<NuqsAdapter
  defaultOptions={{
    shallow: false,        // notify server by default
    scroll: true,          // scroll to top on change
    clearOnDefault: true,  // remove param when equals default
    limitUrlUpdates: throttle(250)  // throttle URL updates
  }}
>
  {children}
</NuqsAdapter>
tsx
import { throttle } from 'nuqs'

<NuqsAdapter
  defaultOptions={{
    shallow: false,        // 默认通知服务端
    scroll: true,          // 状态变更时滚动到顶部
    clearOnDefault: true,  // 值等于默认值时自动移除参数
    limitUrlUpdates: throttle(250)  // 节流控制URL更新频率
  }}
>
  {children}
</NuqsAdapter>

Core API

核心API

Single Parameter

单个参数

tsx
'use client'
import { useQueryState, parseAsInteger } from 'nuqs'

// String (default) - returns null | string
const [search, setSearch] = useQueryState('q')

// With parser + default (recommended)
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))

// Updates
setSearch('hello')           // ?q=hello
setSearch(null)              // removes param
setPage(p => p + 1)          // functional update
await setPage(5)             // returns Promise<URLSearchParams>
tsx
'use client'
import { useQueryState, parseAsInteger } from 'nuqs'

// 字符串(默认类型)- 返回 null | string
const [search, setSearch] = useQueryState('q')

// 搭配解析器+默认值(推荐用法)
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))

// 更新操作
setSearch('hello')           // ?q=hello
setSearch(null)              // 移除参数
setPage(p => p + 1)          // 函数式更新
await setPage(5)             // 返回 Promise<URLSearchParams>

Multiple Parameters

多个参数

tsx
import { useQueryStates, parseAsInteger, parseAsString } from 'nuqs'

const [filters, setFilters] = useQueryStates({
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1),
  sort: parseAsString.withDefault('date')
})

// Partial updates
setFilters({ page: 1, sort: 'name' })

// Await batch update
const params = await setFilters({ page: 2 })
params.get('page')  // '2'
tsx
import { useQueryStates, parseAsInteger, parseAsString } from 'nuqs'

const [filters, setFilters] = useQueryStates({
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1),
  sort: parseAsString.withDefault('date')
})

// 局部更新
setFilters({ page: 1, sort: 'name' })

// 等待批量更新完成
const params = await setFilters({ page: 2 })
params.get('page')  // '2'

Built-in Parsers

内置解析器

ParserTypeExample URL
parseAsString
string
?q=hello
parseAsInteger
number
?page=1
parseAsFloat
number
?price=9.99
parseAsHex
number
?color=ff0000
parseAsBoolean
boolean
?active=true
parseAsIsoDateTime
Date
?date=2024-01-15T10:30:00Z
parseAsTimestamp
Date
?t=1705312200000
parseAsArrayOf(parser)
T[]
?tags=a,b,c
parseAsArrayOf(parser, ';')
T[]
?ids=1;2;3
(custom separator)
parseAsJson<T>()
T
?data={"key":"value"}
parseAsStringEnum(values)
enum
?status=active
parseAsStringLiteral(arr)
literal
?sort=asc
parseAsNumberLiteral(arr)
literal
?dice=6
解析器类型URL示例
parseAsString
string
?q=hello
parseAsInteger
number
?page=1
parseAsFloat
number
?price=9.99
parseAsHex
number
?color=ff0000
parseAsBoolean
boolean
?active=true
parseAsIsoDateTime
Date
?date=2024-01-15T10:30:00Z
parseAsTimestamp
Date
?t=1705312200000
parseAsArrayOf(parser)
T[]
?tags=a,b,c
parseAsArrayOf(parser, ';')
T[]
?ids=1;2;3
(自定义分隔符)
parseAsJson<T>()
T
?data={"key":"value"}
parseAsStringEnum(values)
enum
?status=active
parseAsStringLiteral(arr)
literal
?sort=asc
parseAsNumberLiteral(arr)
literal
?dice=6

Enum & Literal Examples

枚举与字面量示例

tsx
// String enum
enum Status { Active = 'active', Inactive = 'inactive' }
const [status] = useQueryState('status',
  parseAsStringEnum(Object.values(Status)).withDefault(Status.Active)
)

// String literal (type-safe)
const sortOptions = ['asc', 'desc'] as const
const [sort] = useQueryState('sort',
  parseAsStringLiteral(sortOptions).withDefault('asc')
)

// Number literal
const diceSides = [1, 2, 3, 4, 5, 6] as const
const [dice] = useQueryState('dice',
  parseAsNumberLiteral(diceSides).withDefault(1)
)
tsx
// 字符串枚举
enum Status { Active = 'active', Inactive = 'inactive' }
const [status] = useQueryState('status',
  parseAsStringEnum(Object.values(Status)).withDefault(Status.Active)
)

// 字符串字面量(类型安全)
const sortOptions = ['asc', 'desc'] as const
const [sort] = useQueryState('sort',
  parseAsStringLiteral(sortOptions).withDefault('asc')
)

// 数字字面量
const diceSides = [1, 2, 3, 4, 5, 6] as const
const [dice] = useQueryState('dice',
  parseAsNumberLiteral(diceSides).withDefault(1)
)

Arrays

数组

tsx
// Default comma separator: ?tags=react,typescript,nuqs
const [tags, setTags] = useQueryState('tags',
  parseAsArrayOf(parseAsString).withDefault([])
)

// Custom separator: ?ids=1;2;3
const [ids] = useQueryState('ids',
  parseAsArrayOf(parseAsInteger, ';').withDefault([])
)
tsx
// 默认逗号分隔符: ?tags=react,typescript,nuqs
const [tags, setTags] = useQueryState('tags',
  parseAsArrayOf(parseAsString).withDefault([])
)

// 自定义分隔符: ?ids=1;2;3
const [ids] = useQueryState('ids',
  parseAsArrayOf(parseAsInteger, ';').withDefault([])
)

Options

配置项

tsx
useQueryState('key', parseAsString.withOptions({
  history: 'push',       // 'push' | 'replace' (default)
  shallow: false,        // true (default) = client only, false = notify server
  scroll: false,         // scroll to top on change
  throttleMs: 500,       // throttle URL updates (min 50ms)
  clearOnDefault: true,  // remove param when equals default (default: true)
  startTransition,       // React useTransition for loading states
}))
Options precedence: call-level > parser-level > hook-level > global adapter
tsx
// Parser-level options
const parser = parseAsString.withOptions({ shallow: false })

// Hook-level options
const [q, setQ] = useQueryState('q', parser, { history: 'push' })

// Call-level override (highest priority)
setQ('value', { shallow: true })
tsx
useQueryState('key', parseAsString.withOptions({
  history: 'push',       // 取值为 'push' | 'replace' (默认)
  shallow: false,        // 默认为true=仅客户端更新,false=通知服务端
  scroll: false,         // 变更时滚动到顶部
  throttleMs: 500,       // 节流控制URL更新(最小50ms)
  clearOnDefault: true,  // 值等于默认值时移除参数(默认开启)
  startTransition,       // 搭配React useTransition实现加载状态
}))
配置优先级:调用级别 > 解析器级别 > 钩子级别 > 全局适配器
tsx
// 解析器级别配置
const parser = parseAsString.withOptions({ shallow: false })

// 钩子级别配置
const [q, setQ] = useQueryState('q', parser, { history: 'push' })

// 调用级别覆盖(优先级最高)
setQ('value', { shallow: true })

Functional Updates & Batching

函数式更新与批量更新

tsx
// Functional updates
setCount(c => c + 1)
setCount(c => c * 2)  // Both batched in same tick

// Chained functional updates execute in order
function onClick() {
  setCount(x => x + 1)  // 0 → 1
  setCount(x => x * 2)  // 1 → 2
}

// Await updates
const search = await setFilters({ page: 2 })
search.get('page')  // '2'
tsx
// 函数式更新
setCount(c => c + 1)
setCount(c => c * 2)  // 两次更新会在同一事件循环中批量执行

// 链式函数式更新按顺序执行
function onClick() {
  setCount(x => x + 1)  // 0 → 1
  setCount(x => x * 2)  // 1 → 2
}

// 等待更新完成
const search = await setFilters({ page: 2 })
search.get('page')  // '2'

Loading States with useTransition

搭配useTransition实现加载状态

tsx
'use client'
import { useTransition } from 'react'
import { useQueryState, parseAsString } from 'nuqs'

function Search({ results }) {
  const [isLoading, startTransition] = useTransition()

  const [query, setQuery] = useQueryState('q',
    parseAsString.withOptions({
      startTransition,  // enables loading state
      shallow: false    // required for server updates
    })
  )

  return (
    <>
      <input value={query ?? ''} onChange={e => setQuery(e.target.value)} />
      {isLoading ? <Spinner /> : <Results data={results} />}
    </>
  )
}
tsx
'use client'
import { useTransition } from 'react'
import { useQueryState, parseAsString } from 'nuqs'

function Search({ results }) {
  const [isLoading, startTransition] = useTransition()

  const [query, setQuery] = useQueryState('q',
    parseAsString.withOptions({
      startTransition,  // 启用加载状态
      shallow: false    // 服务端更新必填
    })
  )

  return (
    <>
      <input value={query ?? ''} onChange={e => setQuery(e.target.value)} />
      {isLoading ? <Spinner /> : <Results data={results} />}
    </>
  )
}

Custom Parsers

自定义解析器

Basic Custom Parser

基础自定义解析器

tsx
// Simple date parser
const parseAsDate = {
  parse: (value: string) => new Date(value),
  serialize: (date: Date) => date.toISOString().split('T')[0]
}

const [date, setDate] = useQueryState('date', parseAsDate)
tsx
// 简易日期解析器
const parseAsDate = {
  parse: (value: string) => new Date(value),
  serialize: (date: Date) => date.toISOString().split('T')[0]
}

const [date, setDate] = useQueryState('date', parseAsDate)

With createParser (for reference types)

使用createParser(针对引用类型)

For non-primitive types, provide
eq
function for
clearOnDefault
to work:
tsx
import { createParser, parseAsStringLiteral } from 'nuqs'

// Date with equality check
const parseAsDate = createParser({
  parse: (value: string) => new Date(value.slice(0, 10)),
  serialize: (date: Date) => date.toISOString().slice(0, 10),
  eq: (a: Date, b: Date) => a.getTime() === b.getTime()
})

// Complex type (e.g., TanStack Table sort state)
// URL: ?sort=name:asc → { id: 'name', desc: false }
const parseAsSort = createParser({
  parse(query) {
    const [id = '', dir = ''] = query.split(':')
    return { id, desc: dir === 'desc' }
  },
  serialize(value) {
    return `${value.id}:${value.desc ? 'desc' : 'asc'}`
  },
  eq(a, b) {
    return a.id === b.id && a.desc === b.desc
  }
})
对于非原始类型,需要提供
eq
函数以保证
clearOnDefault
功能正常工作:
tsx
import { createParser, parseAsStringLiteral } from 'nuqs'

// 带相等性判断的日期解析器
const parseAsDate = createParser({
  parse: (value: string) => new Date(value.slice(0, 10)),
  serialize: (date: Date) => date.toISOString().slice(0, 10),
  eq: (a: Date, b: Date) => a.getTime() === b.getTime()
})

// 复杂类型(例如TanStack Table排序状态)
// URL: ?sort=name:asc → { id: 'name', desc: false }
const parseAsSort = createParser({
  parse(query) {
    const [id = '', dir = ''] = query.split(':')
    return { id, desc: dir === 'desc' }
  },
  serialize(value) {
    return `${value.id}:${value.desc ? 'desc' : 'asc'}`
  },
  eq(a, b) {
    return a.id === b.id && a.desc === b.desc
  }
})

Server Components (Next.js)

服务端组件(Next.js)

tsx
// lib/searchParams.ts
import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'

export const searchParamsCache = createSearchParamsCache({
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1)
})

// app/search/page.tsx (Server Component)
import { searchParamsCache } from '@/lib/searchParams'
import type { SearchParams } from 'nuqs/server'

type Props = { searchParams: Promise<SearchParams> }

export default async function Page({ searchParams }: Props) {
  // ⚠️ Must call parse() - don't forget!
  const { q, page } = await searchParamsCache.parse(searchParams)
  return <Results query={q} page={page} />
}

// Nested server component - no props needed
function NestedComponent() {
  const page = searchParamsCache.get('page')  // type-safe!
  return <span>Page {page}</span>
}
tsx
// lib/searchParams.ts
import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'

export const searchParamsCache = createSearchParamsCache({
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1)
})

// app/search/page.tsx (服务端组件)
import { searchParamsCache } from '@/lib/searchParams'
import type { SearchParams } from 'nuqs/server'

type Props = { searchParams: Promise<SearchParams> }

export default async function Page({ searchParams }: Props) {
  // ⚠️ 必须调用parse(),不要遗漏!
  const { q, page } = await searchParamsCache.parse(searchParams)
  return <Results query={q} page={page} />
}

// 嵌套服务端组件 - 无需传递props
function NestedComponent() {
  const page = searchParamsCache.get('page')  // 类型安全!
  return <span>Page {page}</span>
}

Reusable Patterns

可复用模式

Shared Parser Definitions

共享解析器定义

tsx
// lib/parsers.ts
export const paginationParsers = {
  page: parseAsInteger.withDefault(1),
  limit: parseAsInteger.withDefault(20),
  sort: parseAsString.withDefault('createdAt'),
  order: parseAsStringLiteral(['asc', 'desc'] as const).withDefault('desc')
}

// Component
const [pagination, setPagination] = useQueryStates(paginationParsers)
tsx
// lib/parsers.ts
export const paginationParsers = {
  page: parseAsInteger.withDefault(1),
  limit: parseAsInteger.withDefault(20),
  sort: parseAsString.withDefault('createdAt'),
  order: parseAsStringLiteral(['asc', 'desc'] as const).withDefault('desc')
}

// 组件中使用
const [pagination, setPagination] = useQueryStates(paginationParsers)

URL Key Mapping

URL键名映射

tsx
const [coords, setCoords] = useQueryStates(
  {
    latitude: parseAsFloat.withDefault(0),
    longitude: parseAsFloat.withDefault(0)
  },
  {
    urlKeys: { latitude: 'lat', longitude: 'lng' }
  }
)
// URL: ?lat=45.5&lng=-122.6
// Code: coords.latitude, coords.longitude
tsx
const [coords, setCoords] = useQueryStates(
  {
    latitude: parseAsFloat.withDefault(0),
    longitude: parseAsFloat.withDefault(0)
  },
  {
    urlKeys: { latitude: 'lat', longitude: 'lng' }
  }
)
// URL: ?lat=45.5&lng=-122.6
// 代码中访问: coords.latitude, coords.longitude

Custom Hook

自定义Hook

tsx
// hooks/useFilters.ts
export function useFilters() {
  return useQueryStates({
    search: parseAsString.withDefault(''),
    category: parseAsString,
    minPrice: parseAsFloat,
    maxPrice: parseAsFloat,
    inStock: parseAsBoolean.withDefault(false)
  })
}

// Component
const [filters, setFilters] = useFilters()
tsx
// hooks/useFilters.ts
export function useFilters() {
  return useQueryStates({
    search: parseAsString.withDefault(''),
    category: parseAsString,
    minPrice: parseAsFloat,
    maxPrice: parseAsFloat,
    inStock: parseAsBoolean.withDefault(false)
  })
}

// 组件中使用
const [filters, setFilters] = useFilters()

Testing

测试

tsx
import { withNuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

it('updates URL on click', async () => {
  const user = userEvent.setup()
  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()

  render(<CounterButton />, {
    wrapper: withNuqsTestingAdapter({
      searchParams: '?count=1',
      onUrlUpdate
    })
  })

  await user.click(screen.getByRole('button'))

  expect(screen.getByRole('button')).toHaveTextContent('count is 2')
  expect(onUrlUpdate).toHaveBeenCalledOnce()

  const event = onUrlUpdate.mock.calls[0]![0]!
  expect(event.queryString).toBe('?count=2')
  expect(event.searchParams.get('count')).toBe('2')
  expect(event.options.history).toBe('push')
})
tsx
import { withNuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

it('点击时更新URL', async () => {
  const user = userEvent.setup()
  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()

  render(<CounterButton />, {
    wrapper: withNuqsTestingAdapter({
      searchParams: '?count=1',
      onUrlUpdate
    })
  })

  await user.click(screen.getByRole('button'))

  expect(screen.getByRole('button')).toHaveTextContent('count is 2')
  expect(onUrlUpdate).toHaveBeenCalledOnce()

  const event = onUrlUpdate.mock.calls[0]![0]!
  expect(event.queryString).toBe('?count=2')
  expect(event.searchParams.get('count')).toBe('2')
  expect(event.options.history).toBe('push')
})

Critical Mistakes to Avoid

需要避免的严重错误

1. Missing Adapter

1. 遗漏适配器

tsx
// ❌ Error: nuqs requires an adapter
useQueryState('q')

// ✅ Wrap app in NuqsAdapter first (see Setup section)
tsx
// ❌ 错误:nuqs需要适配器
useQueryState('q')

// ✅ 先使用NuqsAdapter包裹应用(参考配置章节)

2. Wrong Adapter for Framework

2. 框架适配器不匹配

tsx
// ❌ Using app router adapter in pages router
import { NuqsAdapter } from 'nuqs/adapters/next/app'  // Wrong!

// ✅ Match adapter to your router
import { NuqsAdapter } from 'nuqs/adapters/next/pages'
tsx
// ❌ 在pages router中使用app router的适配器
import { NuqsAdapter } from 'nuqs/adapters/next/app'  // 错误!

// ✅ 选择与你使用的路由匹配的适配器
import { NuqsAdapter } from 'nuqs/adapters/next/pages'

3. Missing Suspense (Next.js App Router)

3. 遗漏Suspense(Next.js App Router)

tsx
// ❌ Hydration error
export default function Page() {
  const [q] = useQueryState('q')
  return <div>{q}</div>
}

// ✅ Wrap client components in Suspense
export default function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <SearchClient />
    </Suspense>
  )
}
tsx
// ❌ 水合错误
export default function Page() {
  const [q] = useQueryState('q')
  return <div>{q}</div>
}

// ✅ 用Suspense包裹客户端组件
export default function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <SearchClient />
    </Suspense>
  )
}

4. Same Key, Different Parsers

4. 同一个key使用不同解析器

tsx
// ❌ Conflicts - last update wins with wrong type
const [intVal] = useQueryState('foo', parseAsInteger)
const [floatVal] = useQueryState('foo', parseAsFloat)

// ✅ One parser per key, share via custom hook
function useFoo() {
  const [val, setVal] = useQueryState('foo', parseAsFloat)
  return { float: val, int: Math.floor(val ?? 0), setVal }
}
tsx
// ❌ 冲突:最后一次更新会覆盖,类型错误
const [intVal] = useQueryState('foo', parseAsInteger)
const [floatVal] = useQueryState('foo', parseAsFloat)

// ✅ 每个key对应一个解析器,通过自定义hook共享
function useFoo() {
  const [val, setVal] = useQueryState('foo', parseAsFloat)
  return { float: val, int: Math.floor(val ?? 0), setVal }
}

5. Forgetting to Parse on Server

5. 服务端忘记调用parse

tsx
// ❌ Returns cache object, not values
const values = searchParamsCache  // Wrong!

// ✅ Call parse() with searchParams prop
const values = await searchParamsCache.parse(searchParams)
tsx
// ❌ 返回缓存对象而非实际值
const values = searchParamsCache  // 错误!

// ✅ 传入searchParams参数调用parse()
const values = await searchParamsCache.parse(searchParams)

6. Server Component with Client Hook

6. 服务端组件使用客户端钩子

tsx
// ❌ useQueryState only works in client components
export default function Page() {  // Server component
  const [q] = useQueryState('q')  // Error!
}

// ✅ Use createSearchParamsCache for server, useQueryState for client
tsx
// ❌ useQueryState仅在客户端组件中生效
export default function Page() {  // 服务端组件
  const [q] = useQueryState('q')  // 报错!
}

// ✅ 服务端使用createSearchParamsCache,客户端使用useQueryState

7. Not Handling Null Without Default

7. 未设置默认值时没有处理null

tsx
// ❌ Tedious null handling
const [count, setCount] = useQueryState('count', parseAsInteger)
setCount(c => (c ?? 0) + 1)  // Must handle null every time

// ✅ Use withDefault
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))
setCount(c => c + 1)  // Always a number
tsx
// ❌ 每次都要处理null,代码冗余
const [count, setCount] = useQueryState('count', parseAsInteger)
setCount(c => (c ?? 0) + 1)  // 每次都要处理null

// ✅ 使用withDefault设置默认值
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))
setCount(c => c + 1)  // 值始终为数字

8. Lossy Serialization

8. 序列化丢失精度

tsx
// ❌ Loses precision on reload
const geoParser = {
  parse: parseFloat,
  serialize: v => v.toFixed(2)  // 1.23456 → "1.23" → 1.23
}

// ✅ Preserve precision or accept the tradeoff knowingly
const geoParser = {
  parse: parseFloat,
  serialize: v => v.toString()
}
tsx
// ❌ 页面重载时丢失精度
const geoParser = {
  parse: parseFloat,
  serialize: v => v.toFixed(2)  // 1.23456 → "1.23" → 1.23
}

// ✅ 保留精度,或明确接受精度损失的权衡
const geoParser = {
  parse: parseFloat,
  serialize: v => v.toString()
}

9. Missing eq for Reference Types

9. 引用类型遗漏eq方法

tsx
// ❌ clearOnDefault won't work correctly
const dateParser = {
  parse: (v) => new Date(v),
  serialize: (d) => d.toISOString()
}

// ✅ Provide eq function for reference types
const dateParser = createParser({
  parse: (v) => new Date(v),
  serialize: (d) => d.toISOString(),
  eq: (a, b) => a.getTime() === b.getTime()
})
tsx
// ❌ clearOnDefault无法正常工作
const dateParser = {
  parse: (v) => new Date(v),
  serialize: (d) => d.toISOString()
}

// ✅ 为引用类型提供eq函数
const dateParser = createParser({
  parse: (v) => new Date(v),
  serialize: (d) => d.toISOString(),
  eq: (a, b) => a.getTime() === b.getTime()
})

Quick Reference

快速参考

TaskSolution
Single param
useQueryState('key', parser.withDefault(val))
Multiple params
useQueryStates({ key: parser })
Server access
createSearchParamsCache
+
.parse()
Notify server
{ shallow: false }
History entry
{ history: 'push' }
Loading state
useTransition
+
{ startTransition }
Short URL keys
urlKeys: { longName: 'short' }
Array param
parseAsArrayOf(parser)
or
parseAsArrayOf(parser, ';')
Enum/literal
parseAsStringLiteral(['a', 'b'] as const)
Custom type
createParser({ parse, serialize, eq })
Test component
withNuqsTestingAdapter({ searchParams: '?...' })
需求解决方案
单个参数
useQueryState('key', parser.withDefault(val))
多个参数
useQueryStates({ key: parser })
服务端获取参数
createSearchParamsCache
+
.parse()
通知服务端更新
{ shallow: false }
新增历史记录
{ history: 'push' }
加载状态
useTransition
+
{ startTransition }
短URL键名
urlKeys: { longName: 'short' }
数组参数
parseAsArrayOf(parser)
parseAsArrayOf(parser, ';')
枚举/字面量
parseAsStringLiteral(['a', 'b'] as const)
自定义类型
createParser({ parse, serialize, eq })
测试组件
withNuqsTestingAdapter({ searchParams: '?...' })