I'm learning firebase authentication in react. I believed onAuthStateChanged only triggers when the user sign-in state changes. But even when I go to a different route or refresh the page, it would still execute.
Here is my AuthContext.js
import React, {useContext,useEffect,useState} from 'react';
import {auth} from './firebase';
import { createUserWithEmailAndPassword, onAuthStateChanged, signInWithEmailAndPassword,
signOut } from "firebase/auth";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({children}) {
const [currentUser,setCurrentUser] = useState();
const [loading,setLoading] = useState(true);
useEffect(()=>{
const unsub = onAuthStateChanged(auth,user=>{
setLoading(false);
setCurrentUser(user);
console.log("Auth state changed");
})
return unsub;
},[])
function signUp(email,password){
return createUserWithEmailAndPassword(auth,email,password)
}
function login(email,password){
return signInWithEmailAndPassword(auth,email,password);
}
function logout(){
return signOut(auth);
}
const values = {
currentUser,
signUp,
login,
logout
}
return <AuthContext.Provider value={values}>
{!loading && children}
</AuthContext.Provider>;
}
I put onAuthStateChanged in useEffect(), so every time the component renders the code inside will run. But why would onAuthStateChanged() still run when user sign-in state does not change? I'm asking this question because it created problems. onAuthStateChanged would first return a user of null. If user is already authenticated, it would return the user a second time. Because of the first "null" user, my other page would not work properly. For example, I have this private router that would always redirect to the login page even when the user is authenticated.
My Private Route
import React from 'react';
import {Route, Navigate} from "react-router-dom";
import { useAuth } from './AuthContext';
export default function Private() {
const {currentUser} = useAuth();
return <div>
{currentUser ? <>something</>:<Navigate to="/login"/>}
</div>;
}
if onAuthStateChanged doesn't trigger when I don't sign in or log out, I wouldn't have the problems mentioned above
CodePudding user response:
I don't think the issue is with onAuthStateChange per se, but rather the fact that you're setting loading to false first, and only setting the current user afterwards. While react attempts to batch multiple set states together, asyncronous callbacks which react is unaware of won't be automatically batched (not until react 18 anyway).
So you set loading to false, and the component rerenders with loading === false, and currentUser is still on its initial value of undefined. This then renders a <Navigate>, redirecting you. A moment later, you set the currentUser, but the redirect has already happened.
Try using unstable_batchedUpdates to tell react to combine the state changes into one update:
import React, { useContext, useEffect, useState } from "react";
import { unstable_batchedUpdates } from 'react-dom';
// ...
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
unstable_batchedUpdates(() => {
setLoading(false);
setCurrentUser(user);
});
console.log("Auth state changed");
});
return unsub;
}, []);
