Next.jsのrouter.queryでURLの動的パラメータを取得するときの注意

業務で、useEffect内でNext.jsのuseRouterを使ってクエリパラメータを取得しようとした際、意図する通りの挙動をしておらず戸惑ったので備忘録としてまとめておきます。

通常**`pages/blogs/[id].tsx`** のようなファイルにおいてAPIからデータを取得したい場合、getStaticPaths・getStaticPropsを使うかと思いますが、今回は画面描画してからバックグラウンドでAPIリクエストを行ってデータ取得する必要があり、そこで見つけたのがuseRouterのrouter.queryでした。

## useRouterとは

そもそもuseRouterとは、Next.jsが用意しているhooksの一つで、routeに関する色んな情報を持つrouterオブジェクトへのアクセスを提供するものです。

> アプリの関数コンポーネント内の`router`オブジェクトにアクセスする場合は、`useRouter`フックを使用できます。
`useRouter`はReactフックです。
> 
[https://nextjs.org/docs/api-reference/next/router#userouter](https://nextjs.org/docs/api-reference/next/router#userouter)

routerオブジェクトはいくつもの便利なメソッドやプロパティを持ちますが、その中のひとつにqueryがあります。

## router.queryの使い方

 `router.query` とするだけで、オブジェクト形式で動的ルーティングの中身を取得できます。

ちなみに以下の例のようにクエリパラメータが数字の場合も、string型で取得されます。

```jsx
import { useRouter } from 'next/router'

// 関数コンポーネント内にて
  const router = useRouter()
  console.log(router.query)
  console.log(router.query.id)

// 出力結果(localhost:3000/blogs/1 の場合)
// { id: '1' }
// 1
```

## useEffect内でrouter.queryを使ってみる

pages/blogs/[id].tsxのuseEffect内でrouter.queryを使ってみます。

```jsx
const router = useRouter()

useEffect(() => {
  const routeId = router.query.id
  console.log(routeId)
}, [])

// undefined
```

出力されたのは `undefinde` でした。

どうやら、router.queryの取得はuseEffectの発火タイミングより後のようです。

【注】ただし、チームの中には上記のコードでもundefindeにならずqueryが取れている方もいましたし、私の環境でも時によってはqueryが取れることもありました。いずれにせよ不安定なものと思われるので、以下の対処をしておくのが無難なのかなと思います。
（何かご存じの方はぜひご指導・ご指摘ください。）

### useEffectの依存配列にrouterを指定する

依存配列にrouterを入れてみました。

```jsx
const router = useRouter() 

useEffect(() => {
  const routeId = router.query.id
  console.log(routeId)
}, [router])

// 出力結果(localhost:3000/blogs/1 の場合)
// undefined
// 1
```

すると、一度undefindeと出た後にqueryが取れてコンソールに出力されました。

これで目的を果たせる場合もあるかもしれません。

しかし今回私が実装したかった要件は、取得したルートパラメーターを使ったパスでAPIへリクエストを送るというものです。
undefinedを取得してしまうと `'/api/blogs/undefined'`のパスにリクエストを送ってしまい、これは不都合でした。

### isReadyを使ってrouter情報が準備されているか判別する

辿り着いたコードは以下の通りです。

```jsx
const router = useRouter()

useEffect(() => {
  if (router.isReady) {
    const routeId = router.query.id
    console.log(routeId)
  }
}, [router])

// 出力結果(localhost:3000/blogs/1 の場合)
// 1
```

routerオブジェクトの持つ `isReady` というboolean値が最適でした。

> `isReady`：`boolean`　ルーターフィールドがクライアント側で更新され、使用できる状態になっているかどうか。`useEffect`メソッド内でのみ使用する必要があり、サーバーで条件付きでレンダリングするためには使用しないでください。
> 
[https://nextjs.org/docs/api-reference/next/router#router-object](https://nextjs.org/docs/api-reference/next/router#router-object)

## なぜundefindeが取得されていたのか

queryの取得タイミングはuseEffectの発火よりも後なのだということは何となく察しましたが、どういう仕組みなのか気になります。

Next.jsのドキュメントのダイナミックルーティングについての箇所に、以下のように書かれていました。

> Pages that are statically optimized by [Automatic Static Optimization](https://nextjs.org/docs/advanced-features/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.
> 

Pre-renderingの間はqueryは空のオブジェクトであり、hydrationのプロセスが終わった段階でqueryオブジェクトが提供されると書いてあります。

getStaticPropsやgetSererSidePropsの処理が終わるまでは、queryは空なのですね。

hydrationという単語は初めて知りましたが、Next.jsの公式の図を見るとイメージは湧きました。

![pre-rendering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/7f7ef04a-1bf8-6e3e-dc19-45d50c8470e8.png)
引用：[https://nextjs.org/learn/basics/data-fetching/pre-renderin](https://nextjs.org/learn/basics/data-fetching/pre-rendering)g


## 参考記事
https://zenn.dev/kiyokiyoabc/articles/d3a8464367094a

https://zenn.dev/luvmini511/articles/1523113e0dec58