So I am authenticating a user with useContext API. Here is my AuthContext code which returns AuthContext.Provider with value.
import { createContext, useEffect, useState } from "react";
const AuthContext = createContext();
export const AuthProvider = ({children}) =>{
const [user, setUser] = useState();
const [role, setRole] = useState();
const [authenticated, setAuthenticated] = useState(false);
useEffect(()=>{
const getUserData = async()=>{
const response = await fetch("http://localhost:5000/api/isAuth", {
method: "GET",
headers: {
"Content-Type":"application/json",
"x-access-token": localStorage.getItem("token"),
}
});
const json = await response.json();
if (json.userData != null) {
setUser(json.userData);
setRole(json.role);
setAuthenticated(true);
}
}
getUserData();
}, []);
return (
<AuthContext.Provider value={{
user,
setUser,
role,
setRole,
authenticated,
setAuthenticated
}}>
{children}
</AuthContext.Provider>
);
}
export default AuthContext;
Now I am wrapping my whole App using AuthProvider
root.render(
<React.StrictMode>
<AuthProvider>
<App />
<AuthProvider>
</React.StrictMode>
);
On dashboard page I am checking for user authentication with "authenticated" state. if authenticated, user will see the dashboard otherwise, he will redirect to login page.
This is my dashboard component.
const {user, setUser, role, setRole, authenticated, setAuthenticated} = useContext(AuthContext);
const navigate = useNavigate();
useEffect(()=>{
if (authenticated == false) {
console.log(authenticated);
return navigate("/login");
}
}, [authenticated])
return (
<div>
{
// console.log(user.name)
}
</div>
)
Now my issue is, On Dashboard page, before the AuthProvider updates value of "authenticated" and "user" states, it immediately checks for the "authenticated" value and navigate to login page. Which is should not be the case in real life. It should at least wait for the change in state.
I tried using useEffect, but nothing happened. I also tried to make default value of authenticated state to be null and when fetch API give response, setting the value as true or false based on result. But it doesn't make any difference.
An also on dashboard page it says "user" is null because it just reads old value of user state before AuthProvider updates, value of "user" state.
Is there any way to solve this? or I am doing something wrong?
You can add an isLoading
state to your context. This will help you distinguish the states.
In the AuthContext
:
import { createContext, useEffect, useState } from "react";
const AuthContext = createContext();
export const AuthProvider = ({children}) =>{
const [user, setUser] = useState();
const [role, setRole] = useState();
const [isLoading, setIsLoading] = useState(true); //default to true
const [authenticated, setAuthenticated] = useState(false);
useEffect(()=>{
const getUserData = async()=>{
const response = await fetch("http://localhost:5000/api/isAuth", {
method: "GET",
headers: {
"Content-Type":"application/json",
"x-access-token": localStorage.getItem("token"),
}
});
const json = await response.json();
if (json.userData != null) {
setIsLoading(false) // set to false after data is fetched
setUser(json.userData);
setRole(json.role);
setAuthenticated(true);
}
}
getUserData();
}, []);
return (
<AuthContext.Provider value={{
user,
setUser,
role,
isLoading,
setRole,
authenticated,
setAuthenticated
}}>
{children}
</AuthContext.Provider>
);
}
export default AuthContext;
Then in the Dashboard
component:
function Dashboard(props) {
const {
user,
setUser,
isLoading,
role,
setRole,
authenticated,
setAuthenticated,
} = useContext(AuthContext);
const navigate = useNavigate();
useEffect(() => {
if (!isLoading) {
if (!authenticated) {
return navigate('/login');
}
}
}, [authenticated, isLoading]);
return <div>{isLoading ? <>Loading...</> : <>Welcome {user.name}!</>}</div>;
}
export default Dashboard;