【React】Firebase Authenticationを例にuseContextの使い方をわかりやすく解説してみた

React HookのuseContextとは

React HookとはReact16.8から追加された、
状態やライフサイクルをClassを書かずに管理するための機能となります。

その中の一つにuseContextという機能があります。

この機能を使うことで、コンポーネント間の値の受け渡しをシンプルにすることができます。

useContextを使わずに値を受け渡す場合、
propsを使って親コンポーネントから子コンポーネントへ値を渡すのが一般的です。

ただし、コンポーネントのツリー構造によっては、
propsの受け渡しが複雑になってしまいます。

propsを使って値を受け渡す場合

例えば、下記のコンポーネント構造があったとしましょう。

  • ComponentA 親コンポーネント
  • ComponentB 子コンポーネント
  • ComponentC 孫コンポーネント

valueを ComponentA から ComponentC へ値を渡したい場合、
直接ComponentAからComponentCへ値を渡すことができません。

一度ComponentBへpropsで値を渡して、ComponentB から ComponentC へと渡す必要があります。

コンポーネントの数が増えて、それぞれのコンポーネントの関係性も複雑になると、
管理が煩雑になり、変更が発生した場合に複数個所の修正が必要となるので、非常に面倒です。

そこで登場するのがuseContextです。

useContextを使った場合

propsで値を受け渡すpropsリレーを避けるために、useContextを使います。

使い方を簡単に説明すると、
受け渡したい値の入ったContextを作り、このContextを参照させるコンポーネントを設定します。

すると、設定されたコンポーネントから値が参照できるようになります。

下記の図ではvalueをComponentA, ComponentB, ComponentCに受け渡しています。

ComponentA,B,Cが親子関係にあったとしても、
各コンポーネント間でpropsのやり取りをすることなく、
直接値を参照することができます。

また、Componentの数が増えた場合でも、
Component設定を追加するだけで簡単に値を渡すことができるので、非常に便利です。

では、実際にuseContextの使い方を見ていきましょう。

useContextの構成

ここでは説明のために簡単に構成でコードを書いてみます。

まずはApp.jsとComponentsディレクトリの中にComponentA,ComponentBを用意しましょう

┣━━components
┃   ┣━━━ComponentA.jsx
┃   ┗━━━ComponentB.jsx
┃
┗━━App.js

親コンポーネント App.js

まずはApp.jsに下記のように書いてベースを作ります。

 import React from 'react';

 function App() {
  return (
   <div>
   </div>
  );
 }

 export default App;

次に以下の記述をします。

  1. useState, createContextをインポート
  2. ComponentA,Bをインポート
  3. Contextを作る
  4. 受け渡す値を設定
  5. nameを表示
  6. 値の受け渡し先を設定
 import React, {useState, createContext } from 'react';    //1.インポート
 import ComponentB from './components/ComponentB';
 import ComponentA from './components/ComponentA';      // 2.インポート

 export const TestContext = creatContext();          // 3.Context作成

 function App() {
  const [name, setName] = useState('')
  const value = {
   name,
   setName,
  };                       // 4.nameとsetNameをvalueに格納して受け渡す 

  return (
   <div>
    <p>{name}</p>                    //5.nameの値を表示する

    <TestContext.Provider value = {value}>
     <ComponentA/>
     <ComponentB/>
    </TestContext.Provider>                //6.値の受け渡し先を設定
   </div>
  );
 }

 export default App;

まず1.ではuseStateとcreateContextをimportしています。

useStateは受け渡したい値を格納するために使用します。

createContextはuseContextを使えるようにするために、contextを作成するために使用します。

2.では値を受け渡すためのコンポーネントをimportしています。

3.ではcontextを作成しています。
TestContextという関数名を付けておりますが、ここは自由な名前の関数に設定することができます。
例えば、後ほど説明するFirebaseの認証情報を格納する場合は AuthContext などの名前を付けてわかりやすくします。

4.ではuseStateを使ってnameという値を用意し、valueにnameを格納しています。
また、nameの書き換えを行うためにsetNameも使用するので、一緒に格納します。

5.はnameを表示するために用意しました。

6.では作成したcontextを使って値を受け渡すコンポーネントを設定しています。
3.で作成した関数名に「.Provider」を付けたタグを用意します。
AuthContextという名前でcreatContextをした場合は、<AuthCotext.Provider></AuthContext.Provider>となります。

この.Providerタグの中に書かれたコンポーネントは
親コンポーネントであるApp.jsの中にあるvalueという関数を
TestContextを引数にして使用することができるようになりました。

これで親コンポーネントの設定は完了です。

子コンポーネント ComponentA

次にComponentAを作ります。
ComponentAのベースを下記のように作ります。

 import React from 'react';

 const ComponentA = () => {
  return (
   <div>
   </div>
  );
 };

 export default ComponentA;

そして下記を追加します。

  1. useContextをインポート
  2. TestContextをインポート
  3. 親コンポーネントで作ったContextを関数に格納する
  4. setNameでuseStateを変更する
 import React, { useContext } from 'react';           //1.インポート
 import { TestContext } from '../App';             // 2.インポート

 const ComponentA = () => {
  const value = useContext(TestContext);         //3.親コンポーネントで作ったvalueを子コンポーネントのvalueに格納する

  return (
   <div>
    <button onClick = {() => value.setName('Tanaka')}>   //4.setNameの実行
     田中
    </button>
   </div>
  );
 };

 export default ComponentA;

1.ではcontext機能を使用するためのuseContextをimportしています。

また、2.では親コンポーネントで作成したcontextをimportしています。
AuthContextなど作成した関数名でimportしてください。

3.ではuseContextを使ってTestContextの中身を取り出し、valueの中に格納しました。
このvalueの中にはnameとsetNameが入った状態になっています。

4.ではボタンタグを用意して、onClickイベントによりsetNameが発動し
nameにTanakaが入るように設定しています。

ComponentAの設定はこれでおしまいです。

子コンポーネント ComponentB

ComponentBもAとおなじように作成します。

 import React, { useContext } from 'react';           //1.インポート
 import { TestContext } from '../App';             // 2.インポート

 const ComponentB = () => {
  const value = useContext(TestContext);          //3.親コンポーネントで作ったvalueを子コンポーネントのvalueに格納する

  return (
   <div>
    <button onClick = {() => value.setName('Yamada')}>   //4.setNameの実行
     山田
    </button>
   </div>
  );
 };

 export default ComponentB;

内容はComponentAとほぼ同じです。

差分はsetNameにYamadaが格納されるだけです。

ここまで完了したらブラウザで確認してみましょう。

上記のようにボタンが二つ表示されています。

また、ボタンを押すと押したボタンに応じて名前がアルファベットで表示されます。

上記のコードはとてもシンプルでuseContextを使うまでもないのですが、
複雑化するとuseContextのありがたみがわかります。

それでは、Firebaseの認証情報を共有するためのcontextを作ってみましょう。

Firebase Authenticatonを使う

【注意事項】Firebaseのバージョンが変わっています!

まず初めに、2021年9月にFirebaseのバージョンがv8からv9に更新されました。

2021年9月以前はv9の仕様はベータ版として存在していましたが、
今回の更新でv9が正式版となりました。

まだ、v9についての情報は多く出回っていませんが、
v8を利用する理由がない限りはv9にて作成することをオススメします。

また、v8にて作成している方も順次v9に置き換えていくことをオススメします。

今後v13くらいまで更新を見込んでいるらしく、
v8のサポートが早々に終わってしまう可能性が非常に高いようです。

本記事ではv8でAuthContextについて紹介した後に、該当箇所をv9にて書き換えます。

contextの記述は同じとなります。

Firebaseのバージョンに関しましてはドキュメントなどと合わせて参考にしていただければ幸いです。

構成

事前にFirebaseの初期設定を済ませた状態からの作成となります。

Firebase初期設定に関しては情報がたくさん公開されているので、
読みやすく理解しやすい記事を見つけてInitialize.AppとAuthentication機能のexportまで完了させてください。

参考までに以下Youtube動画のURLを2つ掲載しておきます。
私が良く参考にしているトラハックさんの動画です。

Firebaseのインストールと初期設定(25分程度の動画)

https://www.youtube.com/watch?v=ta2m6nfYHuQ

Firebase Configの作成とInitializeの方法(動画冒頭10分程度)

https://www.youtube.com/watch?v=PNvLGoop8z8&t=2858s

では、下記の較正にてサンプルコードを作ります。

┣━━conponents
┃   ┗━━━Component.jsx
┃
┣━━context
┃   ┗━━━AuthContext.jsx
┃
┣━━Firebase
┗━━App.js

AuthContext.jsx

まずはAuthContext.jsxを作ります。

必要なものをimportします。

 import React, { createContext, useState, useContext, useEffect } from 'reacr';
 import { auth } from '../firebase';

今回はAuthContextというコンポーネント内でcontextの作成と利用を一括で行い、
そのコンポーネントごと値を渡す書き方をします。

まず1行目で使用するHookをすべてimportしています。

2行目のauthについてはFirebaseのディレクトリ内で、
Firebase.Initialize.appを実行し、authという関数名にてAuthenticationの機能をexportしています。

 import React, { createContext, useState, useContext, useEffect } from 'reacr';
 import { auth } from '../firebase';

 const AuthContext = createContext();

 export function useAuthContext() {
     return useContext(AuthContext);
 }

次に、AuthCotenxtという名前の関数でcreateContextを行いました。

さらに、AuthContextを使用したuseContextが返り値となる、useAuthContextを作成しました。

これは、値を受け渡すComponent.jsxにてimportして、値を受け取るための設定を行います。

また同時に、useAuthContextをexportしています。

 import React, { createContext, useState, useContext, useEffect } from 'reacr';
 import { auth } from '../firebase';

 const AuthContext = createContext();

 export function useAuthContext() {
     return useContext(AuthContext);
 }

 export function AuthProvider({ children }) {
 
 //ここにFirebaseに関連するコードを書きます。

 };

次にもう1つAuthProviderという名前のfunctionを作成し、exportします。

この中身が重要になるので、AuthProviderにフォーカスしてコードを書きます。

 export function AuthProvider({ children }) {
 
  const [user, setUser] = useState('');
  const value = {
   user,
  };

 };

AuthProviderの中に受け渡す値として、userを設定し、valueの中に格納しました。

今回はこのコンポーネント内にてuserを書き換えるので、
setUserはvalueに含みません。

 export function AuthProvider({ children }) {
 
  const [user, setUser] = useState('');
  const value = {
   user,
  };

  useEffect(() => {
   const unsubscribed = auth.onAuthStateChanged((user) => {
    setUser(user);
   });

   return () => {
    unsubscribed();
   };
  },[]);

 };

useEffectを追加しました。

レンダリングが行われた際に、auth.onAuthStateChangedが実行されます。

onAuthStateChangedは実行時に登録されているユーザーのログイン状況を監視して、
ログインされている場合はuserにユーザー情報を渡してくれます。

もしログインされていない場合は、userの中身がnullが入るようになっています。

このauth.onAuthStateChangedの部分がFirebase v8による記述になります。
この部分は後ほどv9の記載をご紹介します。

そして、returnでunsubscribedを空にしています。
これを記述することでログイン状況の監視を終了させます。

また、useEffectの最後にからの配列[]を記述しています。
この記述によりレンダリング時の一度だけunsubscribedを実行することができます。

空の配列を記述していないと無限レンダリングによるエラーが発生しますのでご注意ください。

 export function AuthProvider({ children }) {
 
  const [user, setUser] = useState('');
  const value = {
   user,
  };

  useEffect(() => {
   const unsubscribed = auth.onAuthStateChanged((user) => {
    setUser(user);
   });

   return () => {
    unsubscribed();
   };
  },[]);

  return (
   <AuthContext.Provider value = {value}>
    { children }
   </AuthContext.Provider>
  );

 };

最後にreturnを書いて<AuthContext.Provider>タグにvalueを持たせます。

以上でAuthContextの作成は以上になります。

全体のコードは以下となります。

 import React, { createContext, useState, useContext, useEffect } from 'reacr';
 import { auth } from '../firebase';

 const AuthContext = createContext();

 export function useAuthContext() {
     return useContext(AuthContext);
 }

 export function AuthProvider({ children }) {
 
  const [user, setUser] = useState('');
  const value = {
   user,
  };

  useEffect(() => {
   const unsubscribed = auth.onAuthStateChanged((user) => {
    setUser(user);
   });

   return () => {
    unsubscribed();
   };
  },[]);

  return (
   <AuthContext.Provider value = {value}>
    { children }
   </AuthContext.Provider>
  );

 };

App.jsx

つぎにApp.jsxを作ります。

まずはimportをしましょう。

 import React from 'react';
 import { AuthProvider } from './context/AuthContext';
 import Component from './components/Component

ここでは先ほど作成したAuthContextを該当のコンポーネントからimportします。

また、値を受け渡すコンポ―ネントもimportします。

次にfunction Appとその中にreturnを書きます。

 import React from 'react';
 import { AuthProvider } from './context/AuthContext';
 import Component from './components/Component

 function App () {
  return (

  );
 }

 export default App;

ここから、return内に<AuthProvider>タグを書きます。

そしてタグの中に値を受け渡すコンポーネントを書くことで、
ComponentからAuthContextの値を参照することができるようになります。

 import React from 'react';
 import { AuthProvider } from './context/AuthContext';
 import Component from './components/Component

 function App () {
  return (
   <AuthProvider>

    //ここにコンポーネントを設定
    <Component />

   </AuthProvider>
  );
 }

 export default App;

Component.jsx

最後に値を受け取るコンポーネントを作ります。

まずはimportとexportを書きます。

ここでは、AuthContext にて作成し た、useAuthContextをimportします。

 import React from 'react';
 import { useAuthContext } from './context/AuthContext';

 export default Component;

次にComponentという名前で関数を作成し、その中でuseAuthContextを使用しています。

これによりuserにAuthContextで取得したユーザー情報かもしくはnullの情報が渡されます。

 import React from 'react';
 import { useAuthContext } from './context/AuthContext';
 
 const Component = () => {
  const { user } = useAuthContext();
 };

 export default Component;

このuserを使用して、nullの場合はログインページにリダイレクトさせたり、
userIDを参照してプロフィールを表示させたりするなどの処理を、
Component関数の中に記述します。

Version9の場合

最後にv9での記述を紹介します。

v9の記述をする場合はv9をインストールして使用するか、
Firebaseをimportするさいにcompatと記述してご利用ください。

詳しくは下記の記事を参照してください。

Firebase Javascript SDK v8→v9における進め方と注意事項

https://zenn.dev/mktu/articles/3905b13500ffb6

今回のコードに関してはuseEffect内でFirebaseのAuthentication機能を使用しているので、
そこだけ変更すればv9の記述になります。

コードは以下の通りです。

  useEffect(() => {
   onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });
  }, []);

v8ではauth.onAuthStateChangedと記述していましたが、
先頭のauthが引数としてカッコの中に記述されました。

AuthStateChangedについては記述の変更はこれだけになります。

その他のFirebase機能についても記述方法が変わっているので、
慣れないところもあるかと思いますが、少しずつv9に移行していきましょう。

詳しくは、Firebaseのドキュメントをご確認ください。

Firebase 構築関連のプロダクトについて学ぶ

https://firebase.google.cn/docs/build?hl=ja

まとめ

今回は値を受け渡すために活躍するuseContextの紹介と
Firebaseをに認証情報を共有するサンプルコードを作成しました。

useContextの使用方法まとめ

  • useContextを使うとpropsリレーを回避することができる。
  • コンポーネントの親子関係を無視して直接値を参照できる。
  • createContextでcontextを作成し、context.Providerのタグで値の受け渡し先を設定する。
  • useStateを使って受け渡す値を設定する。
  • useContextで値を受け取る。

useContextを使ったFirebase認証情報のまとめ

  • Firebaseのバージョンは可能な限り最新のv9を使用すること
  • Firebase認証のためにcontext用のコンポ―ネントを作成する。
  • context用コンポーネント内でcreateContextとuseContextをそれぞれ記述する。
  • useContextはuseAuthContextとしてexportしておく。
  • useEffectを使ってFirebaseからユーザー情報を監視・取得する。監視の終了も同時に行う。
  • AuthContext.Providerを返すfunctionを作成し、AuthProviderとしてexportしておく。
  • App.jsx内でAuthProviderを使い、値の受け渡し先となるComponentを設定する。
  • ComponentでuseAuthContextを使い値を受け取り、userに格納する。

useContextの使い方について、私は最初理解ができませんでしたが、
関連記事やYoutubeの動画などから様々な人のuseContextの記述を参考にして、少しずつ理解を深めました。

人によって書き方はさまざまですが、共通する記述が必ずありますので、
まずは共通箇所から理解を深めて、そこから周辺知識を増やしていくことで、
なんとか今回の記事を書くまでに至りました。

かなりの長文となったので、読むのが一苦労かもしれませんが、参考にしていただけると幸いです。

今回も最後まで読んでいただきありがとうございました。

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です