Search code examples
reactjsreact-hooksroutesrenderreact-context

Protected Routes in react router dom 6


I created simple user context in react:

UserProvider

export const AuthContext = createContext();


export const AuthProvider = ({children}) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
   
      //Call server
      const loginUser = {
        userId: 1,
        role: "Admin"
      }
      setUser(loginUser)
    }, []);

  return (
    <AuthContext.Provider value={{user}}  >{children}</AuthContext.Provider>
  );
};

useAuth

const useAuth = () => {
    const user = useContext(AuthContext);
    if (user === undefined) {
      throw new Error('useAuth must be used within an AuthProvider');
    }
    return user;
};

export default useAuth;

App

  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/Login" element={<Login/>}/>
          <Route element={<ProtectedRoutes />}>
            <Route path="/User/Profile" element={<Profile/>}/>
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );

Protected Routes

const ProtectedRoutes = () => {
  const {user} = useAuth();

  return(
    user ? <Outlet/> : <Navigate to="/login"/>
  )

}

export default ProtectedRoutes

Everything works fine, but when I refresh the page in User/Profile when user is logged,it anyway redirects me to login. Console log in Protected routes when i refresh the page looks like this:

null
{userId: 1, role: 'Admin'}

I know that this problem probably occurs because "user" is null when first rendered, but I have no idea how to fix it.Could anyone tell me how i can improve it?


Solution

  • You can keep a app state and redirect only when app is authenticated

    Here is a good article by Kent C. Dodds on how to handle authentication

    export const AuthContext = createContext();
    
    
    export const AuthProvider = ({children}) => {
    
      const [appState, setAppState] = useState({ user: null, state: 'loading' });
    
      useEffect(() => {
       
          //Call server
          const loginUser = {
            userId: 1,
            role: "Admin"
          }
          setUser({
             user: loginUser,
             state: 'loaded'
          })
        }, []);
    
      return (
        <AuthContext.Provider value={appState}  >{children}</AuthContext.Provider>
      );
    };
    
    
    const useAuth = () => {
        const state = useContext(AuthContext);
        if (state === undefined) {
          throw new Error('useAuth must be used within an AuthProvider');
        }
        return { ...state };
    };
    
    export default useAuth;
    

    handling app state in ProtectedRoutes

    const ProtectedRoutes = () => {
    
      const { user, state } = useAuth();
    
      if (state === 'loading') {
        return <div>Loading...</div>
      }
    
      return(
        user ? <Outlet/> : <Navigate to="/login"/>
      )
    
    }
    
    export default ProtectedRoutes
    

    Or rendering the protected routes only after app is loaded

    const AppRoutes = () => {
    
       const { user, state } = useAuth();
       
       if (state === 'loading') {
           return <div>Loading...</div>
       }   
    
       return user ? 
         <AuthenticatedRoutes>
         : UnAuthenticatedRoutes />
    
    }
    
      return (
        <AuthProvider>
          <BrowserRouter>
                <AppRoutes />
            </Routes>
          </BrowserRouter>
        </AuthProvider>
      );
    

    Hope this helps in some way