【Jotai】URLパラメータごとに値を保持する

こんにちは、フリーランスエンジニアの太田雅昭です。

Jotaiのスコープ

Jotaiはデフォルトでグローバルスコープを持ちますが、Providerを使用することで限定することもできます。

検証環境

  • “next”: “15.4.6”
  • “jotai”: “^2.12.5”

URLパラメータごとに保持する

まず下記のように、任意のキーで分離できるProviderを作成します。

const stores = new Map<string, ReturnType<typeof createStore>>()

export function KeyScopedProvider({ scopeKey, children }: { scopeKey: string; children: React.ReactNode }) {
  const store = useMemo(() => {
    let s = stores.get(scopeKey)
    if (!s) {
      s = createStore()
      stores.set(scopeKey, s)
    }
    return s
  }, [scopeKey])
  return <Provider store={store}>{children}</Provider>
}

これを使って、URLパラメータごとに保持する専用のProviderを作成します。

export function SearchParamScopedProvider({ children }: { children: React.ReactNode }) {
  const sp = useSearchParams();

  // 例: 全クエリをキーにする。特定のキーだけにするなら sp.get("roomId") などに変更
  const key = sp.toString();
  return <KeyScopedProvider scopeKey={key}>{children}</KeyScopedProvider>;
}

テストします。下記はnext.jsページの全コードです。

"use client"

import { atom, createStore, Provider, useAtom } from 'jotai'
import Link from 'next/link'
import { usePathname, useSearchParams } from 'next/navigation'
import { useMemo } from 'react'

const ageAtom = atom<number>(0)

export default function App() {
  return <SearchParamScopedProvider>
    <Inner />
  </SearchParamScopedProvider>
}

function Inner() {
  const [age, setAge] = useAtom(ageAtom)
  const searchParams = useSearchParams();
  const name = searchParams.get('name') ?? '';
  const pathname = usePathname();
  return <div>
    <Link href='?name=taro'>taro</Link><br />
    <Link href='?name=jiro'>jiro</Link><br />
    <Link href={`${pathname}/sub`}>Sub page</Link><br />
    <br />
    <br />
    {name}の歳は
    <input type='number' value={age} onChange={(e) => setAge(Number(e.target.value))} />
    です。
  </div>
}


export function SearchParamScopedProvider({ children }: { children: React.ReactNode }) {
  const sp = useSearchParams();

  // 例: 全クエリをキーにする。特定のキーだけにするなら sp.get("roomId") などに変更
  const key = sp.toString();
  return <KeyScopedProvider scopeKey={key}>{children}</KeyScopedProvider>;
}


const stores = new Map<string, ReturnType<typeof createStore>>()

export function KeyScopedProvider({ scopeKey, children }: { scopeKey: string; children: React.ReactNode }) {
  const store = useMemo(() => {
    let s = stores.get(scopeKey)
    if (!s) {
      s = createStore()
      stores.set(scopeKey, s)
    }
    return s
  }, [scopeKey])
  return <Provider store={store}>{children}</Provider>
}

それぞれの名前ごとにageを設定できました。またsubpageから戻ってきた時も値が保持されています。