Home > database >  React context with firebase snapshot on user doc
React context with firebase snapshot on user doc

Time:10-15

For some reason, when inside my function "login", my "currentUser" state always returns null. What I'm trying to do is setting a snapshot on the user document when the user logs in.

I'm also not sure if I can save the unsubscribe function in state like this but that's a problem I didn't have the chance to have yet

My context component looks like this:

import React, { useContext, useState, useEffect } from 'react'
import { auth, db } from '../init/firebase'

const AuthContext = React.createContext()

export function useAuth() {
  return useContext(AuthContext)
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState("")
  const [loading, setLoading] = useState(true)

  // User firebase doc init
  const [userDoc, setUserDoc] = useState({
    loaded: false,
    data: [],
  })
  const [firebaseUnsubscriber, setFirebaseUnsubscriber] = useState(null);

  function signup(email, password) {
    return auth.createUserWithEmailAndPassword(email, password)
  }

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password).then(() => {
      // The problem here is that "currentUser" is always null
      const firebaseUnsubscriber = db.collection("users").doc(currentUser.uid).onSnapshot((doc) => {
        setUserDoc({
          loaded: true,
          data: doc.data(),
        })
      });
  
      setFirebaseUnsubscriber(firebaseUnsubscriber);
    })
  }

  function logout() {
    return auth.signOut().then(() => {
      firebaseUnsubscriber();
    })
  }

  useEffect(() => {
    const unsubscriber = auth.onAuthStateChanged((user) => {
      setCurrentUser(user)
      setLoading(false)
    })

    return unsubscriber
  }, [])

  const value = {
    currentUser,
    userDoc,
    login,
    signup,
    logout,
  }

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  )
}

CodePudding user response:

This is a matter of timing. When you call setCurrentUser or any other setter returned from a useState() call, you don't change the current value of the variable - you instead set the next value of the variable - which won't be used until React decides a rerender is needed.

Instead of making use of currentUser from your state in the then handler of signInWithEmailAndPassword(), you should instead be using the UserCredential object that is contained in the Promise.

function login(email, password) {
  return auth
    .signInWithEmailAndPassword(email, password)
    .then((userCredential) => {
      const firebaseUnsubscriber = db.collection("users")
        .doc(userCredential.user.uid)
        .onSnapshot((doc) => {
          setUserDoc({
            loaded: true,
            data: doc.data(),
          })
        });
  
      setFirebaseUnsubscriber(firebaseUnsubscriber);
    });
}

Now while the above code will function to fix your issue, there are a number of problems that remain:

  • setFirebaseUnsubscriber is storing an unsubscriber function in the state, which would cause a rerender which would mean that the unsubscriber should be invoked, which causes a new unsubscriber to be stored, which would mean that the unsubscriber should be invoked, which causes ... (the user data portion should be in its own useEffect)
  • Errors with retrieving the user's data are silently ignored
  • A newly created user won't have any user data to be loaded (and data will be set to undefined instead of [] like in the initial state)
  • A user who signed out doesn't have their data cleared
  • Your currentUser state variable has a type of User | "" | null. Either user User | null (recommended when using loading) or User | null | undefined.
  • The loading state of the current user isn't exposed to the context consumers
  • The current code makes use of the should-be-considered-deprecated namespaced v8 Web SDK - if writing new code, you should be using the modular v9 Web SDK.

For a somewhat complex, but robust, way of fixing these issues, check out this answer and its version of FirebaseAuthUserContext. At the time of writing, it uses the v8 SDK but I will update it to v9 in the future.

  • Related