using-nuqs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Working with nuqs

使用nuqs进行开发

Manage React state in URL query parameters with nuqs. Covers Suspense boundaries, parsers, clearing state, and deep-linkable dialogs.
使用nuqs在URL查询参数中管理React状态。内容涵盖Suspense边界、解析器、状态清除以及可深度链接的对话框。

Implement Working with nuqs

实现nuqs的使用

Manage React state in URL query parameters with nuqs for shareable filters, search, and deep-linkable dialogs.
See:

使用nuqs在URL查询参数中管理React状态,以实现可共享的筛选器、搜索功能和可深度链接的对话框。
参考:

Suspense Boundary Pattern

Suspense边界模式

nuqs uses
useSearchParams
behind the scenes, requiring a Suspense boundary. Wrap nuqs-using components with Suspense via a wrapper component to keep the boundary colocated:
typescript
import { Suspense } from "react";

type SearchInputProps = {
  placeholder?: string;
};

// Public component with built-in Suspense
export function SearchInput(props: SearchInputProps) {
  return (
    <Suspense fallback={<input placeholder={props.placeholder} disabled />}>
      <SearchInputClient {...props} />
    </Suspense>
  );
}
typescript
"use client";

import { useQueryState, parseAsString } from "nuqs";

// Internal client component that uses nuqs
function SearchInputClient({ placeholder = "Search..." }: SearchInputProps) {
  const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));

  return (
    <input
      value={search}
      onChange={(e) => setSearch(e.target.value || null)}
      placeholder={placeholder}
    />
  );
}
This pattern allows consuming components to use
SearchInput
without adding Suspense themselves.
nuqs在底层使用
useSearchParams
,因此需要Suspense边界。通过包装组件将使用nuqs的组件包裹在Suspense中,使边界与组件就近放置:
typescript
import { Suspense } from "react";

type SearchInputProps = {
  placeholder?: string;
};

// 内置Suspense的公共组件
export function SearchInput(props: SearchInputProps) {
  return (
    <Suspense fallback={<input placeholder={props.placeholder} disabled />}>
      <SearchInputClient {...props} />
    </Suspense>
  );
}
typescript
"use client";

import { useQueryState, parseAsString } from "nuqs";

// 使用nuqs的内部客户端组件
function SearchInputClient({ placeholder = "Search..." }: SearchInputProps) {
  const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));

  return (
    <input
      value={search}
      onChange={(e) => setSearch(e.target.value || null)}
      placeholder={placeholder}
    />
  );
}
这种模式允许消费组件直接使用
SearchInput
,而无需自行添加Suspense。

State to URL Query Params

将状态同步到URL查询参数

Replace
useState
with
useQueryState
to sync state to the URL:
typescript
"use client";

import {
  useQueryState,
  parseAsString,
  parseAsBoolean,
  parseAsArrayOf,
} from "nuqs";

// String state (search, filters)
const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));

// Boolean state (toggles)
const [showArchived, setShowArchived] = useQueryState(
  "archived",
  parseAsBoolean.withDefault(false),
);

// Array state (multi-select)
const [tags, setTags] = useQueryState(
  "tags",
  parseAsArrayOf(parseAsString).withDefault([]),
);
使用
useQueryState
替代
useState
,将状态同步到URL:
typescript
"use client";

import {
  useQueryState,
  parseAsString,
  parseAsBoolean,
  parseAsArrayOf,
} from "nuqs";

// 字符串状态(搜索、筛选器)
const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));

// 布尔状态(开关)
const [showArchived, setShowArchived] = useQueryState(
  "archived",
  parseAsBoolean.withDefault(false),
);

// 数组状态(多选)
const [tags, setTags] = useQueryState(
  "tags",
  parseAsArrayOf(parseAsString).withDefault([]),
);

Clear State

清除状态

Set to
null
to remove from URL:
typescript
// Clear single param
setSearch(null);

// Clear all filters
function clearFilters() {
  setSearch(null);
  setTags(null);
  setShowArchived(null);
}
When using
.withDefault()
, setting to
null
clears the URL param but returns the default value.
设置为
null
即可从URL中移除参数:
typescript
// 清除单个参数
setSearch(null);

// 清除所有筛选器
function clearFilters() {
  setSearch(null);
  setTags(null);
  setShowArchived(null);
}
当使用
.withDefault()
时,设置为
null
会清除URL参数,但会返回默认值。

Deep-Linkable Dialogs

可深度链接的对话框

Control dialog visibility with URL params for shareable links:
typescript
import { Suspense } from "react";

type DeleteDialogProps = {
  onDelete: (id: string) => Promise<void>;
};

// Public component with built-in Suspense
export function DeleteDialog(props: DeleteDialogProps) {
  return (
    <Suspense fallback={null}>
      <DeleteDialogClient {...props} />
    </Suspense>
  );
}
typescript
"use client";

import { useQueryState, parseAsString } from "nuqs";
import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog";

function DeleteDialogClient({ onDelete }: DeleteDialogProps) {
  const [deleteId, setDeleteId] = useQueryState("delete", parseAsString);

  async function handleDelete() {
    if (!deleteId) return;
    await onDelete(deleteId);
    setDeleteId(null);
  }

  return (
    <AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
      <AlertDialogContent>
        {/* Confirmation UI */}
        <Button onClick={handleDelete}>Delete</Button>
      </AlertDialogContent>
    </AlertDialog>
  );
}
Open the dialog programmatically:
typescript
// Open delete dialog for specific item
setDeleteId("item-123");

// Deep link: /items?delete=item-123
使用URL参数控制对话框的可见性,实现可共享的链接:
typescript
import { Suspense } from "react";

type DeleteDialogProps = {
  onDelete: (id: string) => Promise<void>;
};

// 内置Suspense的公共组件
export function DeleteDialog(props: DeleteDialogProps) {
  return (
    <Suspense fallback={null}>
      <DeleteDialogClient {...props} />
    </Suspense>
  );
}
typescript
"use client";

import { useQueryState, parseAsString } from "nuqs";
import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog";

function DeleteDialogClient({ onDelete }: DeleteDialogProps) {
  const [deleteId, setDeleteId] = useQueryState("delete", parseAsString);

  async function handleDelete() {
    if (!deleteId) return;
    await onDelete(deleteId);
    setDeleteId(null);
  }

  return (
    <AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
      <AlertDialogContent>
        {/* 确认UI */}
        <Button onClick={handleDelete}>Delete</Button>
      </AlertDialogContent>
    </AlertDialog>
  );
}
以编程方式打开对话框:
typescript
// 打开对应特定项目的删除对话框
setDeleteId("item-123");

// 深度链接:/items?delete=item-123

Opening Dialogs from Buttons

通过按钮打开对话框

Use a trigger button to open the dialog:
typescript
function ItemRow({ item }: { item: Item }) {
  const [, setDeleteId] = useQueryState("delete", parseAsString);

  return (
    <Button variant="ghost" onClick={() => setDeleteId(item.id)}>
      Delete
    </Button>
  );
}

使用触发按钮打开对话框:
typescript
function ItemRow({ item }: { item: Item }) {
  const [, setDeleteId] = useQueryState("delete", parseAsString);

  return (
    <Button variant="ghost" onClick={() => setDeleteId(item.id)}>
      Delete
    </Button>
  );
}

References

参考资料