javascriptreactjsreact-hooks

how to wait for state change of useState in reactJS to check if User is authenticated or not on Dashboard? with Context API


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?


Solution

  • 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;