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
}