url-state-patterns について
React開発者向けに、nuqsでアプリの状態をURLクエリパラメーターと同期する方法を示すスキルです。パーサー、Suspenseラッパー、パラメーターの消去、URLで開けるダイアログを扱います。
主な機能
- 01クエリパラメーターとの状態同期
- 02型付きクエリパーサーの利用
- 03Suspenseによるコンポーネントのラップ
- 04nullによるパラメーター消去
- 05URLでのダイアログ状態管理
活用シーン
- 01検索内容をURLで共有
- 02パーサー付きのクエリ値でフィルターを表現
- 03ディープリンクから特定項目のダイアログを開く
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ andrelandgraf/ fullstackrecipes --skill url-state-patternsインストール後、Claude Code で/url-state-patternsを入力して読み込まれたか確認します。
詳細
URL の状態管理パターン
nuqs で React の状態を URL のクエリパラメーターと同期します。
前提条件
まず、次のセットアップレシピを完了してください。
- nuqs による URL の状態管理
Suspense ラッパー
nuqs は useSearchParams を読み込むため、Suspense 境界が必要です。公開ラッパーをエクスポートして、その内部のクライアントコンポーネントをサスペンドさせることで、コンポーネントを利用する側で Suspense を追加せずに済みます。
import { Suspense } from "react";
type SearchInputProps = { placeholder?: string };
export function SearchInput(props: SearchInputProps) {
return (
<Suspense fallback={<input placeholder={props.placeholder} disabled />}>
<SearchInputClient {...props} />
</Suspense>
);
}
"use client";
import { useQueryState, parseAsString } from "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}
/>
);
}
パーサー
useState をパーサーと組み合わせた useQueryState に置き換えます。.withDefault() を使うと、URL をすっきり保ったままフォールバック値を読み取れます。
"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([]),
);
クリア
値を null に設定すると、URL から削除されます。.withDefault() を使っている場合、パラメーターはクリアされますが、読み取り時にはデフォルト値が返されます。
setSearch(null);
function clearFilters() {
setSearch(null);
setTags(null);
setShowArchived(null);
}
ディープリンク可能なダイアログ
URL パラメーターからダイアログの表示状態を制御すると、共有でき、戻る/進む操作でも状態が保持されます。同じ Suspense パターンでラップしてください。
import { Suspense } from "react";
type DeleteDialogProps = { onDelete: (id: string) => Promise<void> };
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>
<Button onClick={handleDelete}>Delete</Button>
</AlertDialogContent>
</AlertDialog>
);
}
パラメーターを設定すれば、どこからでもダイアログを開けます。setDeleteId("item-123") を実行すると、ディープリンク /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 対応