mcpField
U
using-nuqs

using-nuqs

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

using-nuqs について

React 開発者向けに、nuqs で UI の状態を URL クエリパラメーターと同期する方法をまとめたスキルです。Suspense 境界、パーサー、状態の消去、共有可能なダイアログを扱います。

主な機能

  1. 01React の状態を URL パラメーターと同期
  2. 02パーサーとデフォルト値の利用
  3. 03単一または複数パラメーターの消去
  4. 04ディープリンク可能なダイアログの作成
  5. 05Suspense 境界の局所化

活用シーン

  1. 01共有可能な検索・フィルター URL の作成
  2. 02リンクからダイアログを開く
  3. 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 の状態を管理し、共有可能なフィルターや検索、ディープリンク可能なダイアログを実現します。

参照:


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>
  );
}

参考資料

コメント