mcpField
U
url-state-patterns

url-state-patterns

github.com/andrelandgraf開発2397 回インストール20 週間前に更新
リポジトリ

url-state-patterns について

React開発者向けに、nuqsでアプリの状態をURLクエリパラメーターと同期する方法を示すスキルです。パーサー、Suspenseラッパー、パラメーターの消去、URLで開けるダイアログを扱います。

主な機能

  1. 01クエリパラメーターとの状態同期
  2. 02型付きクエリパーサーの利用
  3. 03Suspenseによるコンポーネントのラップ
  4. 04nullによるパラメーター消去
  5. 05URLでのダイアログ状態管理

活用シーン

  1. 01検索内容をURLで共有
  2. 02パーサー付きのクエリ値でフィルターを表現
  3. 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>
  );
}

参考資料

コメント