[Firebase/React] Hooksを使ってFirebaseのUser authを管理する

October 05, 2021

programming
article cover

Firebase with hooks

ログインステータスやユーザー情報はグローバルステートとして持っていた方が便利。 かつてはReduxで管理したものだけど、今はReduxを導入する場合はかなり少なくなり、Hooksが台頭している。
今回はFirebaseのユーザー認証をContext APIとHooksで管理する。

作るものは主に以下の3つだけ

  • initFirebase.js
  • useFirebase.js
  • authProvider.js

1. initFirebase.js

initFirebase.jsはその名のとおり、Firebaseを初期化してexportするだけのjsファイル。 Firebaseが初期化されていない場合だけ動作するようにしておかないと、意図せず何度も初期化してしまうので注意。

Gatsbyの場合はtypeof window !== 'undefined'を使ってブラウザ上でのみ動作するように制限する。 こうしておかないとBuild時にエラーになってしまう。

// initFirebase.js

import firebase from 'firebase/app'
import 'firebase/auth'

const firebaseConfig = {
  apiKey: '************',
  authDomain: '************',
  projectId: '************',
  storageBucket: '************',
  messagingSenderId: '************',
  appId: '************',
}

let firebaseInstance

export const initFirebase = () => {
  if (typeof window !== 'undefined') {
    firebaseInstance ??= firebase.initializeApp(firebaseConfig)
  }

  return firebaseInstance
}

2. useFirebase.js

useFirebase.jsでは初期化したFirebaseをstateとしてexportする。

アプリ内でfirebaseを使いたい時はこのhookを通して使う。

// useFirebase.js

import { useState, useEffect } from 'react'
import { initFirebase } from '.initFirebase'

export const useFirebase = () => {
  const [firebase, setFirebase] = useState(null)

  useEffect(() => {
    setFirebase(initFirebase())
  }, [])

  return { firebase }
}

3. authProvider.js

最後にContext providerを作る。
onAuthStateChanged()を使うことでユーザーのステータスの変化を検知することができる。 これを使ってstateのuserInfoを更新する。
さらにonAuthStateChanged()firebase.Unsubscribeを返すので、ComponentがUnmountされるタイミングでUnsubscribeしておく。

firebaseは最初nullの可能性があるので、useEffectの第2引数で[firebase]を指定しておくのを忘れずに。こうしておけばfirebaseの中身が入ったタイミングでもう一度useEffectが走る。

initializingのフラッグは無くてもいいが、あると便利。

// authProvider.js

import React, { useState, createContext, useEffect, useMemo } from 'react'
import { useFirebase } from './useFirebase'

const initialValue = {
  userInfo: null,
  initializing: true,
}

export const AuthContext = createContext(initialValue)

export const AuthContextProvider = ({ children }) => {
  const { firebase } = useFirebase()
  const [initializing, setInitializing] = useState(true)
  const [userInfo, setUserInfo] = useState(null)

  useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
      setUserInfo(user || null)
      setInitializing(false)
    })

    return () => unsubscribe()
  }, [firebase])
  
  const value = useMemo(
    () => ({
      userInfo,
      initializing,
    }),
    [userInfo, initializing]
  )

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

あとは作ったContextProviderでアプリ全体を包めば完成。

// index.js

import React from 'react'
import { AuthContextProvider } from './authProvider'
import YourApp from './yourApp'

export default () => {
  return (
    <AuthContextProvider>
      <YourApp />
    </AuthContextProvider>
  )
}

How to use

あとはuseContext()を使えば好きな場所でユーザーの状態を取得できるし、その状態は常に更新される。 initializingのフラッグがあればロード画面などを表示できるので、設定しておいても別に損はない。

import React, { useContext } from 'react'
import { AuthContext } from './authProvider'
import { Spinner } from './spinner'

export const Component = () => {
  const { userInfo, initializing } = useContext(AuthContext)
  
  if (initializing) return <Spinner />
  
  return userInfo ? <Something /> : null
}

Profile picture

元カメラマン。今はベルリンで働くWEBエンジニア。
スロバキア人の妻とドイツ猫二匹の多国籍家族。