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]);