Hooksだけでfirebase authentication(SDK v9)の認証状態を確認・維持する

## これは何

[react-redux-firebase](http://react-redux-firebase.com/) がfirebase SDK v9のモジュラー実装に非対応な為、使おうとするとcompatライブラリを入れるとか、型情報を書き換えるとかする必要があり大変不便。

そこで、別の手段で認証状態を監理できないか探ったところ、公式のやり方でreduxを経由せずともグローバルで認証状態をチェックできそうだったので記事にした。

要らない情報は余計だと思うので、結論・解説→経緯 の順で記載する。

## 結論・解説

firebaseの `onAuthStateChanged` をカスタムhooksのuseEffect内で使用すれば、グローバルステート監理など気にする必要もなく、特に問題なく認証状態を任意のコンポーネントでチェックできます。

必ずuseEffectの返り値として、 `onAuthStateChanged` の返り値を指定してください。observerが増殖してリソースを食い荒らします。

```ts
import {
  getAuth,
  onAuthStateChanged,
} from 'firebase/auth';
import type { User } from 'firebase/auth';
import { useEffect, useState } from 'react';

export const useAuth = (requireAuth = true) => {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    const auth = getAuth(firebaseApp);
    // onAuthStateChanged の返り値はUnsubscribe関数
    return onAuthStateChanged(auth, (user) => {
      if (user) {
        setUser(user);
        user.getIdToken().then((token) => {
          setToken(token);
        });
      } else {
        setUser(null);
        setToken(null);
      }
    });
  }, []);

  return {
    token,
    user,
  };
};
```

上記例ではuseAuthというカスタムHooksを定義して、ここからcurrentUserとtokenを取ってくるhooksを作るものとして書いてます。

ここの肝は、 `useEffect` 内で `onAuthStateChanged` を実行し、その返り値を `useEffect` の返り値として設定している。 `onAuthStateChanged` はobserber関数なので、これにより、 `useEffect` がrenderされたタイミングで `onAuthStateChanged` の監視がスタートし、 `useEffect` が再renderされる際に自動的にobserverの監視が終了し、再render後に監視が再スタートする。

実際、firebaseのドキュメントでもこの `onAuthStateChanged` を用いて認証状態を監視することをおすすめしている。これは、「認証が済んでいるかまだなのか「を「チェックしているかどうか」の状態(firebaseでは中間状態と呼んでいる)をstate監理する手間が手動でやると発生してしまう為である。

https://firebase.google.com/docs/auth/web/manage-users?hl=ja#get_the_currently_signed-in_user

ユーザーに関するステート監理も含めてfirebase側に移譲することで、firebase側の認証状態と状態監理ライブラリ側の認証状態が乖離する状態が防げるわけである。

なお、上記実装では結局currentUserの情報をhooks内でstate監理してしまっているが、 `onAuthStateChanged` が認証状態チェック前にもcurrentUserを返してくれるのか、うまく動いてくれているのでヨシとする。

## 経緯

### react-redux-firebaseがSDK v9非対応

冒頭の通り、 [react-redux-firebase](http://react-redux-firebase.com/) を導入しようと考えていた、

だが、実装したところ、Vscode×Typescriptによる入力補完でそのまま使うと動かなくなってしまった。

https://github.com/prescottprue/react-redux-firebase/issues/1145

で　書かれている内容を用いれば直せるらしいのだが、僕は直せなかった(どうやっても `firebase.auth` が無いと言われる)ので、そもそもそんな古いバージョンから差し替えが進まないのなら自分で実装しちゃえばいいじゃない！というわけで自前でreact-reduxを実装しようとなった。

### reduxで実装してみたが

当初の実装では、

1. (useAuth内)reduxのラッパーhooks内で、render時にsigninCheckをdispatchする
2. (useAuth内)currentUserが無ければログインページへ遷移
3. signinをdispatchするとログインしてcurrentUser更新
4. signoutをdispatchするとログアウトしてcurrentUserをnullへ更新

こうしたところ、signinCheckをdispatchする前のstateはnullにせざるを得なかったので、signinCheckがdispatchされる前の状態のstateを元にリダイレクト処理が走ってしまった。

また、これを解消するために、stateに `signingState` を追加して、チェックが済んでいる場合にのみリダイレクト処理するような実装をしてもうまく動いてくれなかった。signinCheckのタイミングによっては、firebase側からcurrentUserが飛んで来ないので、ログインチェックはうまく機能しなかった。

### ドキュメントを見つめ直してみる

結論で既に述べた通り、firebaseのドキュメントでは認証の中間状態を管理する手間を省くため、 `onAuthStateChanged` を推奨している、

ところで、この `onAuthStateChanged` はfirebase authenticationの認証状態を監視するobserver関数であるので、関数の返り値はobserberをsubscribeする関数が返ってくるのである。

ならば、useEffect内でこいつごと返せば済む話では？となり、結論が出たわけである。

## 実装した感想

firebase authenticationだけを使って、バックエンドは別途用意するパターンがネット上の記事に本当になかなか出てこなくて、あってもfirebase独自の用語が分かっていること前提の実装だったり、実装例が無いもんだからここにたどり着くまで時間が掛かってしまった。

同じようなことしたい民に刺さってほしい。