ブラウザバックで出てくるアレをReactHooksで実装する方法

この記事は [Ateam Lifestyle Inc. Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-lifestyle) 8日目の記事です。

サイトからブラウザバックした時に、元のページに戻らずにポップアップなどが出たりするサイトを見かけたことがあると思います。この記事では、ブラウザバックした際に特定のコンテンツを表示する機能をReactHooksで実装する方法をお伝えします。

## 注意点

今回の実装では、ユーザーがWebサイトに訪れて何もせずにブラウザバックした場合、そのままユーザーはWebサイトから離脱することができます。ブラウザバック時にコンテンツを表示させられるのは、スクロールやクリック、タップなのどの操作を行ったユーザーに限ります。また、ユーザーは元のページに戻るためにブラウザバックをしているわけです。それを抑止してまで表示すべき、ユーザーにとって有益なコンテンツなのかをよく考え、実装を判断してください。

## 実際のコード

最初に完成したコードをお見せします。

```react:/src/hooks/use-exit.js
import { useEffect, useState } from 'react';

export const useExit = () => {
  const [isExit, setIsExit] = useState(false);

  useEffect(() => {
    if (window === undefined) return;

    const state = window.history.state;
    if (!state) {
      window.history.replaceState({ isExit: true }, '');
      window.history.pushState({ isExit: false }, '');
    }

    const exit = (e) => {
      setIsExit(e?.state?.isExit || false);
    }

    window.addEventListener('popstate', exit);
    return () => {
      window.removeEventListener('popstate', exit);
    }
  }, []);
  return [isExit, setIsExit];
}
```

```react:/src/app.js
import { useExit } from "./hooks/use-exit";

export default function App() {
  const [isExit] = useExit();

  return (
    <div className="App">
      <h1>Hello World!</h1>
      {isExit ? <p>ブラウザバックした！</p> : <p>ブラウザバック前</p>}
    </div>
  );
}
```

こちらのコードについて細かく見ていきましょう。

## コードの解説



`use-exit.js`の`useEffect()`内で`History API`を利用しています。
`History API`はブラウザのセッション履歴操作に関するメソッドやプロパティが提供されています。

https://developer.mozilla.org/ja/docs/Web/API/History_API

まず初めに`History.state`を取得します。ブラウザのセッション履歴はスタックとして保存されています。そのスタックの最上位、すなわち現在の状態を取得します。まだ何も操作していないので基本的には`null`となっているはずですが、何かしらのルーティングライブラリを利用している場合はすでに値が入っている場合もありますので注意してください。

```react
if (window === undefined) return;

const state = window.history.state;
```

続いてキモとなる、ブラウザバック後であることを判定するための初期設定を行います。`state`オブジェクトに`isExit`プロパティが設定されていないようであれば、設定処理を行います。

初めに`history.replaceState()`メソッドで現在のセッション履歴を編集します。
`isExit`プロパティを追加します。値は`true`にします。

そして`history.pushState()`メソッドでセッション履歴スタックに状態を追加します。
同じく`isExit`プロパティを現在の状態に追加するのですが、値は`false`にします。

```react
if (!state) {
  window.history.replaceState({ isExit: true }, '');
  window.history.pushState({ isExit: false }, '');
}
```

今の状態を図にすると下記のような感じです。
セッション履歴が変更されていますが、ページが更新されることはありません。
加えて再レンダリングが走ることも無いため、見た目上の変化はありません。

![Frame 18 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/d1869ec3-4a9f-405d-4f7b-a020ea050aef.png)



最後にブラウザバック時にReactの`isExit`ステータスを更新する処理を追加します。

```react
const exit = (e) => {
  setIsExit(e?.state?.isExit || false);
}

window.addEventListener("popstate", exit);
```

`addEventListener()`をつかって`popstate`のイベントハンドラとして登録します。
`popstate`イベントは戻るボタンや進むボタンを押した時に呼び出されます。

https://developer.mozilla.org/ja/docs/Web/API/WindowEventHandlers/onpopstate

`exit()`メソッドでは`popstateEvent`を受け取ります。この`popstateEvent`には現在の状態が含まれています。
ただし、ブラウザによって`popstate`イベントが発生しても`state`が取得できない場合があるため、オプショナルチェーンでを使って、`e`もしくは`e.state`が存在しない場合はReactの`isExit`ステータスは`false`とします。`e.state.isExit`が`true`であればそのままReactの`isExit`ステータスも`true`となります。先程セッション履歴を変更したため、戻るボタンを押した場合、`exit()`メソッドが取得する`state`は`isExit`プロパティが`true`となっています。

![Frame 20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/74136fb0-ec6a-f92c-5ecf-959ccc397725.png)

このようにHistoryAPIとReactHooksを利用することで、Reactでブラウザバックを判定することができます。

```react
isExit ? <p>ブラウザバックした！</p> : <p>ブラウザバック前</p>
```


## 終わりに

今回はブラウザバックで出てくるアレをReactHooksで実装する方法をお伝えしました！
皆様よいReactライフをお過ごしください！！