Home > Enterprise >  REACT FIREBASE : Manage user data with useContext
REACT FIREBASE : Manage user data with useContext

Time:09-21

I am using firebase for authentication in my react application. I have an AuthContext as:

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

const AuthContext = React.createContext();

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

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

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setCurrentUser(user);
      
      db.collection('users')
      .doc(user.uid)
      .onSnapshot((doc) => {
        var data = doc.data();
        setCurrentUserData(data);
      });
      setLoading(false);
    });
    return unsubscribe;
  }, []);
  
  signup function,
  login function,
  logout function

  const value = {
    currentUser,
    currentUserData,
    signup,
    login,
    logout,
  }
  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

Whenever I signup/create a new user, I am also creating a document in 'users' collection in firestore with some default user fields along with uid from the firebase auth. I have a bunch of pages that requires some data from this firestore document for the current user that is currently logged in. I can get this user data in the currentUserData state for the first user that logs into the application. But when a user logs out and another user logs in, the currentUserData state holds the values fetched from firestore for the previously logged in user, not for the currently logged in user. I tried having a separate DataContext as well like:

import React, { useEffect, useState, useContext } from 'react';
import { db } from '../firebase';
import { useAuth } from './AuthContext';

const DataContext = React.createContext();

export function useData() {
  return useContext(DataContext);
}

export function DataProvider({ children }) {
  const [currentUserData, setCurrentUserData] = useState();
  const [loading, setLoading] = useState(true);

  //current user from AuthContext
  const { currentUser } = useAuth();

  useEffect(() => {
    db.collection('users')
      .doc(currentUser.uid)
      .onSnapshot((doc) => {
        var data = doc.data();
        setCurrentUserData(data);
        setLoading(false);
      });
  }, []);

  const value = {
    currentUserData,
  };

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

And in the corresponding App.js:

 <Router>
      <AuthProvider>
       <DataProvider>
        <Switch>
          <PrivateRoute exact path='/' component={Home} />
                 .
                 .
                 .
         </Switch>
        </DataProvider>
      </AuthProvider>
    </Router> 

Doing this also results in the same scenario. When a user logs out and another logs in, I still have the values of the previous user in the currentUserData state. However, the currentUser state in AuthContext works correcly. I am tired of fetching the user data in each and every component that needs user data for the logged in user. I wanted to fetch the data once and use it in whatever component that needs it. But I am having issues making it work. Is it something I am doing incorrectly?

CodePudding user response:

You could use a reducer something like this (don't have your '../firebase' directory obviously to test):

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

const AuthContext = React.createContext();

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

const initialValue = {
  isAuthenticated: false,
  user: null
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN':
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user
      }
    case 'LOGOUT':
      return {
        ...state,
        isAuthenticated: false,
        user: null
      }
    case 'SIGNUP':
      // do something
      return
    // etc
    default:
      return state;
  }
}

export function AuthProvider({children}) {

  const [authState, authDispatch] = useReducer(reducer, initialValue)

  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // I can't test the following
    const unsubscribe = auth.onAuthStateChanged((user) => {

      db.collection('users')
          .doc(user.uid)
          .onSnapshot((doc) => {
            var data = doc.data();
            authDispatch({
              type: 'LOGIN',
              payload: {
                user: data
              }
            });
          });
      setLoading(false);
    });
    return unsubscribe;
  }, []);

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

and then when you need the context:


const MyComponent = () => {
  
  const {authState, authDispatch} = useAuth();
  
  const handleLogout = (e) => {
    authDispatch({
      type: e.target.value
    })
  }
  
  return (
      <div>
        My UID is {authState.user.uid} <button onClick={handleLogout} value={'LOGOUT'}/>
      </div>
  );
};

CodePudding user response:

Have you tried passing currentUser in the dep array

useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setCurrentUser(user);
      
      db.collection('users')
      .doc(user.uid)
      .onSnapshot((doc) => {
        var data = doc.data();
        setCurrentUserData(data);
      });
      setLoading(false);
    });
    return unsubscribe;
  }, [currentUser]);
  • Related