認証用の Context を Hooks で

React で認証用の Context を作れたら便利そうだなと思ったので、開発イメージを掴むために Hooks も活用して認証用のコードを書いてみました。

Context と Hooks

Context はコンポーネントツリーでの所謂「バケツリレー」をスキップして子孫のコンポーネントに値を渡すことのできる機能です。
Hooks は Functional Component で State や Lifecycle などを使うことのできる機能です。

従来は Context を使うと Consumer の子要素では関数を埋め込む必要があって少々散らかった見た目になってしまいますが、 useContext を使えば見た目がスッキリします。(ただしコンポーネント自身がすでに Provider の中にあるという条件が増える)

Auth

話は変わり、 Redux で Auth 回りの実装をしていると、 Auth から取得できる情報が必要な処理、例えばログイン時にしか見られないページの制限やユーザーの名前を表示する場所、などが出てきます。これらが出てくる度に Redux の connect でつなぐ必要があり面倒です。( React Redux の対応 で変わる可能性はある)
Hooks の公式ドキュメントにもあるように、 auth と Context 、 Context と Hooks の組み合わせは良いはずです。

ただし、今は Redux で API への認証用トークンが必要にであろうと思うので、トークンを利用する Redux と Auth 用 Context を共存するのは厳しいと思われます。
もし Redux を useReducer + Context で簡単に置き換えていたり Suspense for Data Fetching が安定版でリリースされたら便利な方法の一つになるかもしれません。

実装例

例のごとくサンプルプロジェクトを作りました。

Context を作る

Context を作るのはとても簡単ですが、初期値を何にすべきか考えるのは少々面倒です。
この辺を隠蔽できるようにモジュールを作れば便利そうではありますが。

src/AuthContext.tsxlink
15
16
17
18
19
20
21
22
23
const context = React.createContext<AuthContext>({
info: null,
login(): void {
throw Error("Not Implemented!");
},
logout(): void {
throw Error("Not Implemented!");
}
});

Provider を作る

認証回りの機能だけを提供する Renderless Component として AuthProvider を定義します。
useState により作った state から useMemo を介して生成される info と、 Provider コンポーネント内で定義した本物の loginlogout 関数を提供しています。詳しくはリンク先を参照のこと。

src/AuthProvider.tsxlink
25
26
27
<AuthContext.Provider value={{ login, logout, info }}>
{children}
</AuthContext.Provider>

Consumer を使う

const { info, login, logout } = useContext(AuthContext); と使って値(と関数)を得ます。他の記事でも注意書きされていますが、 useContext へは AuthContext.Consumer ではなく AuthContext そのものを渡します。

これを AuthProvider 以下のコンポーネントの任意の場所に置くだけで、 info !== null という判定で 認証時ページ を作ったり、 info!トークンやユーザ名を扱えたり、もちろん、 loginlogout によりログインログアウトもできたりします。 connect を使うよりもずっと簡単です。

毎回 useContext(AuthContext) と書くのが面倒であれば、 useAuth などというカスタムフックにして利用するのも手かと思います。