【React】ブラウザバックで詰まった話

こんにちわ。にっぱちです。
現在業務でReactを触っており結構詰まった件について書いていきます。

やりたいこと

あるページだけブラウザバックイベントを感知してコールバックを実行するというものです。
以下のようにページA、ページB、ページCと3つのページがあったとして、ページBからブラウザバックした時だけ処理を行いたい状況です。

const PageA = () => {
  return (
    <h1>page a</h1>
    <Link to="/page-b">PageB</Link>
  )
}

const PageB = () => {
  return (
    <h1>page b</h1>
    <Link to="/page-c">PageC</Link>
  )
}

const PageC = () => {
  return (
    <h1>page c</h1>
  )
}

やったこと

まず普通にイベントリスナーを追加してみました。

const PageB = () => {
  const callback = (event) => {
    alert('もどる')
  }
  useEffect(() => {
    window.onpopstate = callback
  })
  return (
    <h1>page b</h1>
    <Link to="/page-c">PageC</Link>
  )
}

やりたいことはできましたが、遷移先のページCから戻ってもアラートが表示されました。spaゆえにイベントが生きてる状態なのでしょう。
ページ破棄のタイミングでイベントを削除してみましたが、今度はイベントが発火しなくなりました。

useEffect(() => {
  window.onpopstate = callback
  return () => window.onpopstate = null
})

遷移先のページCでイベントを削除してやるとページBのみイベントが発火して今回やりたいことはできました。ただ依存関係は作りたくなく、あくまでページB内で完結したいです。

最終的な形

ページ破棄のタイミングでイベントを削除してみましたが、今度はイベントが発火しなくなりました。

まずこの問題を解決します。最終的にはこうです。

const callback = (event) => {
  alert('もどる')
  history.back()
}

useEffect(() => {
  history.pushState(null, null, null)
  window.onpopstate = callback
  return () => window.onpopstate = null
})

history.pushState(null, null, null)でページ遷移なく履歴スタックを追加することができます。さらにコールバック関数内でブラウザバック処理を追加し、これによりページBから戻った時イベントが発火されました。
しかし、ページCから戻り、さらに戻るとページAを通り過ぎさらに前のページが表示されました。
原因としてはページCから戻ってページBが表示されるときに新たに履歴スタックが追加されたことでした。そのためスタックを積み上げる条件を指定しました。

const callback = (event) => {
  alert('もどる')
  history.back()
}

useEffect(() => {
  // ページAから遷移したときだけ履歴スタックを追加する
  if (!('dummy' in history.state)) {
    history.pushState({dummy: ''}, null, null)
  }
  window.onpopstate = callback
  return () => window.onpopstate = null
})

これでうまくいきました。

感想

History APIという存在を知り勉強なりました。
また、SPAも初挑戦なのでこれからもがんばりたいです。