Next.jsのrouterがstateをサポートしていないのでクエリを書き換えて対抗する

2021/05/12に公開

Next.jsのrouterにはstateがない

自分は今まで全く意識したことがありませんでしたが、Next.jsのrouterにはstateがありません。

一覧ページから個別ページへ遷移してブラウザバックしたときに、一覧ページでのフィルタや表示順などの状態がリセットされてしまうと言われて初めて気が付きました。

通常ならHistory APIにもあるページに紐づくstateにページの状態を保存することで、ブラウザバックなどで巻き戻すことができます。ちなみにReact RouterにもHistory APIのstateに相当する機能があるようです。

しかしNext.jsのAPI Refarenceを見ても、stateのようなAPIが見当たらないです。Next.jsのことなのでおそらく最適化やシンプル化のためにサポートしていなさそうなので代わりにURLを書き換えて状態を保持することにします。

実際に考えてみると、フィルタや表示順程度の状態であればクエリで十分表現できるのと、URLに埋め込まれていたほうがブックマークしたり共有しやすいので、多少自力で実装する必要が出てくるものの、routerのstateはあまり必要ないですね。

代わりにクエリでページの状態を保持する

数パターン実装してみて自分はこのような形に落ち着きました。

状態を切り替えたときにクエリを書き換えるようにして、useStateのデフォルト値をクエリから読み込みことでURLをベースにして状態を維持します。

useQueryState.ts
import { useState, useCallback } from 'react'
import { useRouter } from 'next/router'

export const useQueryState = <State extends string>(name: string, defaultState: State) => {
  const router = useRouter()

  const [state, setState] = useState(() => {
    const queries = router.query[name]
    const query = Array.isArray(queries) ? queries[0] : queries
    return query ? (query as State) : defaultState
  })

  const toggle = useCallback(
    (newState: State) => {
      setState(newState)
      const query = { ...router.query, [name]: newState }
      router.replace({ query }, undefined, { scroll: false })
    },
    [name, router],
  )

  return [state, toggle] as const
}

このコードはシンプルさを保つために最低限の機能しか実装していませんが実際に使うときは、クエリで想定されてない値が指定されたときにデフォルト値にフォールバックしたりするような処理も必要になると思います。

使い方

Discussion