求められた仕様
クエリパラメータ関連
- URLのクエリパラメータに合わせて表示を変更する
- 画面操作でクエリパラメータを変更できる
ブラウザ操作関連
- ブラウザの戻るボタンを押したら前のクエリパラメータの状態に戻る
- リロードしても表示は変わらない
使用技術
- ReactとTypeScript を使用する
- 標準的なライブラリであるReact Routerの最新バージョンを使用する
- パッケージは
react-router-dom
のv6
を使用する
- パッケージは
実装
配置
弊社のGitHubに配置している。
コード
string
とnumber
の2つの型でクエリパラメータを値として管理する。
複数のファイルで使用できるよう、カスタムフックで実装した。
useQuery.tsx
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
export function useQuery() {
const [searchParams, setSearchParams] = useSearchParams();
const [str, setStr] = useState<string>('');
const [num, setNum] = useState<number>(0);
const getParam = useCallback(
(key: string) => {
return searchParams.get(key) || '';
},
[searchParams],
);
const castStrToNum = useCallback((s: string) => {
switch (s) {
case 'NaN':
return Number.NaN;
default:
return Number(s);
}
}, []);
useEffect(() => {
setStr(getParam('str'));
setNum(castStrToNum(getParam('num')));
}, [getParam, castStrToNum]);
const handleChangeParams = useCallback(
(newStr: string, newNum: number) => {
setSearchParams({ str: newStr, num: String(newNum) });
},
[setSearchParams],
);
return { str, num, castStrToNum, handleChangeParams };
}
結論
-
useSearchParams
の-
searchParams
をuseEffect
の依存配列に含むことでクエリパラメータの変更を検知できる -
setSearchParams
でクエリパラメータを変更する
-
以上が結論です。
以降は余談になります。
よくない例
最初は以下のように実装していた。
useQuery.tsx
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
export function useQuery() {
const location = useLocation();
const [str, setStr] = useState<string>('');
const [num, setNum] = useState<number>(0);
useEffect(() => {
const params = new URLSearchParams(location.search);
const newStr = params.get('str');
const newNum = params.get('num');
setStr(newStr === null ? '' : newStr);
setNum(newNum === null || newNum === '' ? 0 : newNum);
}, [location]);
const [searchParams, setSearchParams] = useSearchParams();
const onChangeParams = (newStr: string, newNum: number) => {
searchParams.set('str', newStr);
searchParams.set('num', newNum);
setSearchParams(searchParams);
};
return { str, num, onChangeParams };
}
特に以下がよくなかった。
- 変更検知のためだけに
useLocation()
を使用している - クエリパラメータ取得のためだけに
URLSearchParams()
を使用している - クエリパラメータ変更のためだけに
useSearchParams()
を使用している
疑問
クエリパラメータ関連のオブジェクトの違い
- 実装するなかで以下の3つを触ったが、それぞれどう異なるのか
-
searchParams = URLSearchParams()
のsearchParams
-
[searchParams, setSearchParams] = useSearchParams()
のsearchParams
-
location = useLocation()
のlocation.search
-
URLSearchParams
- 以下のように使用する:
const searchParams = new URLSearchParams(); const newSearchParams = new URLSearchParams(searchParams); newSearchParams.get('str'); newSearchParams.set('str', '1');
- Reactに依存しないWeb APIの一部
- URLのクエリ文字列を解析して操作するためのインターフェースを提供する
- 任意のクエリ文字列を受け取って解析し、
URLSearchParams
オブジェクトを作成する - そのオブジェクトからクエリパラメータの取得・変更などができる
useSearchParams
- 以下のように使用する:
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams(searchParamsInit); setSearchParams({ str: '1', num: '1' });
- React Router v6 で導入されたフック
- URL のクエリ文字列を解析して操作するためのインターフェースを提供する
- 任意のクエリ文字列を受け取って解析し、
URLSearchParams
オブジェクトとSetURLSearchParams
メソッドを作成する - そのオブジェクトからクエリパラメータを取得し、そのメソッドからクエリパラメータを変更できる
- React の状態として扱われているため、クエリパラメータを変更することがコンポーネントの状態変更となり、関連するコンポーネントが再レンダリングされる
useLocation
- 以下のように使用する:
const location = useLocation(); return <div>Query String: {location.search}</div>;
- React Router v5 以前から存在するフック
- 現在の URL の
location
オブジェクトを提供する - そのオブジェクトからは、URL のパス、クエリ、ハッシュなど、現在の URL について様々な情報を取得できる
- 取得できるのみで、操作や管理には直接関与しない
結論
-
URLSearchParams
は純粋なJavaScrip の機能で、任意のクエリ文字列を解析・操作するために使用される -
useSearchParams
はReact Routerの機能で、クエリパラメータの管理をReactコンポーネントに統合し、状態の更新と連動させるために使用する -
useLocation
はReact Routerの機能で、現在のクエリを提供するが、提供する情報が多いため、追加の操作が必要となる
結果として、今回はReactを使用しているので、状態の更新とレンダリングを連動させるためには、 useSearchParams
がクエリ変更検知元のオブジェクトとクエリ変更方法として最適となった。
逆にReactでURLSearchParamsやuseLocationを使う場合って、どんなとき? [調査中]
- URLSearchParams は useSearchParams を使うほどでもないときに使いそう
- そんなことってあるか??
- 取得だけして変更を検知しないならありえる??
- useLocation は URL 全体を見るときに使いそう
- そんなことってあるか??
- 各種 URL 情報を一括して取得したいときに使いそう
課題
プロジェクトによるが、以下の場合については処理を考える必要があるかもしれない。
- 必要なクエリパラメータが存在しない場合
- クエリパラメータが空の場合
- 不要なクエリパラメータが設定されている場合