I'm using firebase for user authentication and react-router-dom 6 for private routes. The "/account" page is protected and wrapped inside private routes. I have a Nav component, which has an icon that redirects to the "/account" page, the code is as follows:
export default function Nav() {
const navigate = useNavigate();
return (
<>
<nav className='navbar'>
<div className="account-container">
<RiUser3Fill className='account-icon menu-icon' onClick={()=>{navigate("account")}}/>
<BsCartFill className='cart-icon menu-icon' onClick={()=>{navigate('cart')}}/>
</div>
</nav>
<Outlet/>
</>
)
}
When I click on the account icon and the user is logged in, the page would redirect to the protected account page. But the problem is, when I refresh the page at "/account", or type in the URL to get to "/account", the page would always be redirected to "/signin" page even when the user is already signed in. Below are my other components:
App.js:
function App() {
return <BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/" element={<Nav/>}>
<Route element={<PrivateRouter/>}>
<Route path="/account" element={<Account/>}/>
<Route path="/cart" element={<Cart/>}/>
</Route>
<Route path='/signup' element={<Signup/>}/>
<Route path='/signin' element={<Signin/>}/>
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
}
PrivateRouter.jsx:
export default function PrivateRouter() {
const {currentUser} = useAuth();
const location = useLocation();
if(!currentUser) return <Navigate state={{from:location}} to="/signin"/>
return <Outlet />
}
AuthContext.js:
import React, {useContext, useEffect, useState} from 'react';
import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged,
passwo } from 'firebase/auth';
import { auth } from '../utility/firebase';
const AuthContext = React.createContext();
export function useAuth(){
return useContext(AuthContext);
}
export default function AuthProvider({children}) {
const [currentUser, setCurrentUser] = useState();
useEffect(()=>{
const unsub = onAuthStateChanged(auth,user=>{
setCurrentUser(user);
})
return unsub;
},[])
function signin(email,password){
return signInWithEmailAndPassword(auth,email,password);
}
function signup(email,password){
return createUserWithEmailAndPassword(auth,email,password);
}
function signout(){
return signOut(auth);
}
const values = {
currentUser,
signin,
signup,
signout
}
return <AuthContext.Provider value={values}>
{children}
</AuthContext.Provider>;
}
CodePudding user response:
The currentUser
initial value is undefined until updated by the auth check in the useEffect
.
const [currentUser, setCurrentUser] = useState(); // <-- undefined
useEffect(() => {
const unsub = onAuthStateChanged(auth, user => {
setCurrentUser(user); // <-- sets user state after initial render
});
return unsub;
}, []);
So when refreshing the page, i.e. remounting the app, the currentUser
condition in the auth check is falsey and user is bounced to login/signin page.
If the currentUser
is still undefined, i.e. the app hasn't determined/confirmed either way a user's authentication status, you should return null and not commit to redirecting or allowing access through to the routed component.
export default function PrivateRouter() {
const { currentUser } = useAuth();
const location = useLocation();
if (currentUser === undefined) return null; // or loading spinner, etc...
return currentUser
? <Outlet />
: <Navigate to="/signin" replace state={{ from: location }} />;
}