using-nuqs について
React 開発者向けに、nuqs で UI の状態を URL クエリパラメーターと同期する方法をまとめたスキルです。Suspense 境界、パーサー、状態の消去、共有可能なダイアログを扱います。
主な機能
- 01React の状態を URL パラメーターと同期
- 02パーサーとデフォルト値の利用
- 03単一または複数パラメーターの消去
- 04ディープリンク可能なダイアログの作成
- 05Suspense 境界の局所化
活用シーン
- 01共有可能な検索・フィルター URL の作成
- 02リンクからダイアログを開く
- 03URL からフィルターを消去
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ andrelandgraf/ fullstackrecipes --skill using-nuqsインストール後、Claude Code で/using-nuqsを入力して読み込まれたか確認します。
詳細
nuqs を使った作業
nuqs を使って、URL のクエリパラメーターで React の状態を管理します。Suspense 境界、パーサー、状態のクリア、ディープリンク可能なダイアログについて説明します。
nuqs を使った作業を実装する
nuqs を使って URL のクエリパラメーターで React の状態を管理し、共有可能なフィルターや検索、ディープリンク可能なダイアログを実現します。
参照:
- リソース:Fullstack Recipes の
using-nuqs - URL:https://fullstackrecipes.com/recipes/using-nuqs
Suspense 境界のパターン
nuqs は内部で useSearchParams を使用するため、Suspense 境界が必要です。ラッパーコンポーネントを使って、nuqs を使用するコンポーネントを Suspense で囲み、境界をコンポーネントの近くに配置します。
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>
);
}
"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}
/>
);
}
このパターンを使えば、利用側のコンポーネントで Suspense を追加せずに SearchInput を使用できます。
状態を URL クエリパラメーターに同期する
useState を useQueryState に置き換えて、状態を URL に同期します。
"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([]),
);
状態をクリアする
null を設定すると、URL から削除されます。
// Clear single param
setSearch(null);
// Clear all filters
function clearFilters() {
setSearch(null);
setTags(null);
setShowArchived(null);
}
.withDefault() を使用している場合、null を設定すると URL パラメーターはクリアされますが、デフォルト値が返されます。
ディープリンク可能なダイアログ
URL パラメーターでダイアログの表示状態を制御し、共有可能なリンクを作成します。
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>
);
}
"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 delete dialog for specific item
setDeleteId("item-123");
// Deep link: /items?delete=item-123
ボタンからダイアログを開く
トリガーボタンを使ってダイアログを開きます。
function ItemRow({ item }: { item: Item }) {
const [, setDeleteId] = useQueryState("delete", parseAsString);
return (
<Button variant="ghost" onClick={() => setDeleteId(item.id)}>
Delete
</Button>
);
}
参考資料
コメント
Markdown 対応