やりたいこと
Next.jsで書かれたサイトに、確認ページ付きのフォームを実装する。
- フォーム(A)→確認ページ(B) の、ページを跨いだデータ共有にReduxを使う
- しかし、**それ以外のページ(C)**に遷移した時には、フォーム内容に紐づいたReduxのストアを消去しないといけない。
環境
- Node.js 12.18.4
- npm 6.14.8
- Typescript 4.0.7
- React.js 16.13.0
- React Redux 7.2.3
- Redux Toolkit 1.5.1
- Next.js 10.1.3
Before
Reduxでデータを保持するだけだと、**確認ページ(B)**にはきちんと入力した内容が反映されるが、**トップページ(C)に一度戻ってから入力ページ(A)**に再度進んだ時に前回の入力内容が残ってるのでダメ。
Next.js ドキュメントを読んでみると…
Next.js Documents | next/router # router.events
router.events
を使うことで、ページ遷移時のイベントハンドラを登録できるらしい。
この機能を盛り込んだCustom Hookを次のように定義した。
import { useRouter } from "next/dist/client/router";
import { useEffect } from "react";
type RouteChangeAction = (
pathName: string,
options: { shallow?: boolean }
) => void;
type PathnamePredicate = (pathName: string) => boolean;
// どんなPathnameが来ても真
const anyPath: PathnamePredicate = () => true;
/**
* Route変更時に、pathnameが条件を満たす場合にのみアクションを実行するように設定するHook
* @param action Route変更時に呼び出されるアクション
* @param pathPred アクションが呼び出されるかどうかを決める条件 デフォルトでは常に真
*/
const useActionOnRouteChange = (
action: RouteChangeAction,
pathPred: PathnamePredicate = anyPath
) => {
// routerオブジェクトを使うためのおまじない
const router = useRouter();
useEffect(() => {
const handler: RouteChangeAction = (path, options) => {
if (pathPred(path)) action(path, options);
};
// mount時にイベントハンドラを登録
router.events.on("routeChangeStart", handler);
return () => {
router.events.off("routeChangeStart", handler);
};
}, [action, pathPred]); // 一応引数に与えられる関数が変わった時にも再設定する。
};
export default useActionOnRouteChange;
このHookを、遷移前のページ(今回はフォーム入力ページ(A))で使用すると、第2引数で与えた条件を満たすPathname(ここでは確認ページ以外のページ(C))に遷移したときに、第1引数で与えたアクションが実行される。
const InputNamePage = () => {
// ...
const dispatch = useDispatch();
useActionOnRouteChange(
() => {
console.log("reset");
// reduxのActionをここから呼び出す
},
(pathName) => pathName !== "/input-name/confirm" // このPath以外に遷移したとき
);
// ...
return (
<div>
{ /* ... */ }
</div>
);
}
export default InputNamePage;
結果
一度トップページ(C)に戻ると、ちゃんとフォームがリセットされた。
ページ遷移の時のデータ消去は、中央管理にしても良いかもしれないが、この方法を使うと、消したいデータを入力する画面の定義の中に消去のロジックも書くことになり、凝集度が高くなって良いかもしれない。
中央管理にするには、_app.tsx
の中でuseActionOnRouteChange
を使えば良いでしょう。
Custom Hookにすれば、ロジックだけを取り出して、部品化できる!
Hooks, バンザイ!
参考にした記事