Next.js(App Router)のuseSearchParams()の値をPage Propsとおなじフォーマットに変換する

2024/12/17に公開

Next.js(App Router)ではServer ComponentsとClient Componentsでルーティング情報が取得方法が微妙にことなります。

Server Componentsの場合

Server ComponentsではPage ComponentのPropsとしてparamssearchParamsなどのルーティング情報が取得できます。

Next15以降では以下のようなページを作成して

src/app/[...slug]/page.tsx
type Props = {
  params: Promise<{ [key: string]: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}

export default async function Page(props: Props) {
  const params = await props.params
  const searchParams = await props.searchParams

  return (
    <div>
      <p>params : {params.slug}</p>
      <p>searchParams : {searchParams.query}</p>
    </div>
  );
}

http://localhost:3000/foo/bar?query=abc などのURLにアクセスするとブラウザには以下のように表示されます。

params : [foo, bar]
searchParams : abc

※ Next14ではparams、searchParamsはPromiseではないので注意してください。

Client Componentの場合

Client ComponentではuseParams()useSearchParams()を利用してルーティング情報が取得できます。

ただし、useSearchParams()で取得できるオブジェクトはURLSearchParamsなのでPage ComponentのPropsと形を合わせるには少し調整が必要です。

entries()を利用してイテレーターとして取り出し、Object.fromEntries()を利用してオブジェクトに変換することで同じような値を取得できます。

Object.fromEntries(useSearchParams().entries())

つまり以下のようにするとPage ComponentのPropsと同じような値になります。

src/app/[...slug]/Clinent.tsx
export default function ClinentComponent() {
  const params = useParams();
  const searchParams = Object.fromEntries(useSearchParams().entries())

  return (
    <div>
      <p>params : {params.slug}</p>
      <p>searchParams : {searchParams.query}</p>
    </div>
  );
}

重複するquery文字列に対応

http://localhost:3000/foo/bar?query=abc&query=123などの重複するquery文字列がある場合に上記のコードでは共通になりません。

以下のようになりClient Componentでは重複するquery文字列が省略されてしまいます。

params : [foo, bar]
searchParams : [abc, 123]

params : [foo, bar]
searchParams : 123

これを防ぐにはentries()で取得せずにforEach()で取得してgetAll()で複数のquery文字列があるかどうかをチェックしながらsearchParamsを組み立てていきます。

src/app/[...slug]/Clinent.tsx
export default function ClinentComponent() {
  const params = useParams();

  const searchParams: { [key: string]: string[] | string } = {};
  const searchParamsObject = useSearchParams();
  searchParamsObject.forEach((_, key) => {
    const values = searchParamsObject.getAll(key);
    searchParams[key] = values.length > 1 ? values : values[0];
  });

  return (
    <div>
      <p>params : {params.slug}</p>
      <p>searchParams : {searchParams.query}</p>
    </div>
  );
}

このようにすることでNext.js(App Router)でServer ComponentsとClient Componentsでルーティング情報を同じ型で取り扱うことができるようになります。

株式会社トゥーアール

Discussion