🎢

Next.jsのuseRouterを使って、router.queryをuseEffectで使おうとすると一癖必要な話

2021/12/03に公開

Next.jsでAPIをfetchする際にクエリパラメータを使用したいときってありますよね。
今回はnextのuseRouterを使ってuseEffect内でqueryを取得しようとしてまあまあ詰まった話を共有します。

useRouterとは

Next.js公式が出しているHooksの一つで、routeに関する情報が詰まったrouterオブジェクトへのアクセスを可能にするものです。
https://nextjs.org/docs/api-reference/next/router#router-object

今回はrouter.query、つまりURL上のクエリパラメータが入った変数についてです。

useEffectでrouter.queryを取得するとどうなるか

例としてURLpathはapp/exampleでqueryはlimit=30です

app/example/?limit=30

const router = useRouter();
const query = router.query;

useEffect(() => {
console.log(query.limit)
},[]);

console

???

なぜかconsoleはundefinedと表示されます。おかしいな、、、

useEffectの第二引数を指定してないから?

どうやらrouter.queryの取得タイミングとuseEffectの発火タイミングは違っていて、後者の方が早そうです。

こういう時は値が取得されるまで待つに限ります。useEffectの第二引数にqueryを指定して
undefined -> 30になった際に取得できるようにしましょう

app/example/?limit=30

const router = useRouter();
const query = router.query;

useEffect(() => {
console.log(query.limit)
},[query]);

console

成功...か?

どうやらlogは2回吐かれているようです。
1回目がundefinedで2回目が30ですね。

これはuseEffectの仕様で、第二引数に値を設定した場合、マウント時に値が作成されたときと、値の変化時の2回実行されるためです。

最終的な値は想定通りの値になったので問題ないですね!めでたしめでたし

問題になる場合

queryはAPIをfetchするために使用することが多いと思います。その際に、queryがundefined30の二つが生成されるとどのような問題があるでしょうか。

queryが変化すると再実行されるHooksの場合

この場合はqueryが二つ生成されているので2回実行されることになります。そしてもし一回一回が非常に大きなデータ取得を目的としたものだったらどうでしょうか。最悪2倍のデータ量を取得することになるのでこれは問題と言えるのではないでしょうか。筆者も今回queryが変化すると再実行されるようなHooksを使用していたため、困ったことになりました。

クエリを使用するタイミングをuseRouterがクエリを取得したタイミングにしよう

undefinedは取得しないようになんとか変更したいですが、そもそもなぜuseRouterがクエリを取得するタイミングとuseEffectが発火するタイミングが違うのでしょうか。

Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).
After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

https://nextjs.org/docs/routing/dynamic-routes#caveats

Next.jsではPre-renderingという機能を提供しています。これは、Reactのコンポーネントを初期化して双方向バインディングの設定を行う(これをhydrationといいます)前に、あらかじめHTML要素をレンダリングしておいてくれるというものです。
これを用いることで、例えばSPAにおいてファーストビューが遅かったり、SEOに適さない形になるのを防ぐことができます。
より細かい説明が日本語で知りたい方はこちらの記事をどうぞ。
https://zenn.dev/luvmini511/articles/1523113e0dec58
そんな便利な機能ですが、Pre-renderingされたページはその後のhydrationが終わり、アプリケーションの更新があるまで、queryなどが空のオブジェクトであることが、上の公式ページで書かれています。

useEffectはcomponentDidMountのタイミングで初回の発火がありますが、どうやらその時はNext.jsのいうアプリケーションの更新には含まれず、router.queryは空のままということがわかりました。

では、どうすればundefinedではなく、正しいqueryを取得できるのでしょうか。

実は、公式が素晴らしいboolean値を用意してくれています

isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
https://nextjs.org/docs/api-reference/next/router

こちらを使えばrouter.queryに値が入ったタイミングで取得できることがわかりますね。

app/example/?limit=30

const router = useRouter();
const query = router.query;

useEffect(() => {
if(router.isReady) console.log(query.limit);
},[query, router]);

console

無事router.queryを正しく取得することができました。

Discussion