[Firebase/React] Handle Firebase user auth with React hooks

October 05, 2021

programming
article cover

Firebase with hooks

We’d better have data such as login status or user info as global state.
It used to be done with Redux but now React hooks is replacing it, and we rarely use Redux. So let’s handle Firebase auth using React Context API and hooks.

What we’re gonna make is following three files.

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

1. initFirebase.js

In initFirebase.js, we literally initialize Firebase and just export it. That’s it.
We only initialize when it isn’t yet, otherwise we end up initializing Firebase many times.

With Gatsby, we need to do that only on browsers to avoid errors on build. Using typeof window !== 'undefined' helps you to distinguish browser or not.

// 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

In useFirebase.js, export initialized Firebase as a React state.

If you wanna use Firebase inside your app, use this 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

Lastly, Context provider.

onAuthStateChanged() allows us to detect changes of user status. Let it keep userInfo updated. Besides, onAuthStateChanged() returns firebase.Unsubscribe, so do unsubscribe when the component will be unmounted.

firebase could be null at first, keep in mind to specify [firebase] as the second argument of useEffect, so useEffect will run again when firebase is filled.

initializing flag is not necessary but convenient.

// 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>
}

The one last thing to do is wrap your app with the context provider.

// index.js

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

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

How to use

Now we can use userInfo anywhere in our app using useContext(). And userInfo will be always updated.

If you have initializing flag, it’s gonna be handy to show a loading component or something.

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

Photographer / Web engineer working in Berlin.
A Japanese living with a Slovak wife and two German cats.