Search code examples
javascriptreactjsreact-router-dom

React Router Dom Private Route not Working


I am trying to implement a private route towards my application, and while it correctly reaches the private route when authenticated, it redirects it to the login page rather than the children. I've tried every solution on stackoverflow but they don't seem to work. It's strange because it reaches the right path (I print to console whenever I reach the private route) but it's not able to redirect the page correctly.

PrivateRoute

import { useState, useEffect } from 'react';
import { useRef } from 'react';
import { Outlet, Navigate, useLocation } from 'react-router-dom';
import Axios from "axios";
import Cookies from "universal-cookie";

export default function ProtectedRoute({ children }) {
  const [isAuthenticated, setIsAuthenticated] = useState();
  // add loading state, initially true for initial render
  const [isLoading, setIsLoading] = useState(true);
  const checkAuth = async () => {
    const cookie = new Cookies(); 
    setIsLoading(true); // <-- set true when starting auth check
    try {
      if (cookie.get("stytch_session") == null) {
        console.log("no cookies!")
        setIsAuthenticated(false);
      } else {
        Axios.post(
          "http://localhost:5001/test",
          {},
          { headers: { sessiontoken: cookie.get("stytch_session") } }
        )
          .then((response) => {
            console.log("reaching private route!")
            setIsAuthenticated(true);
          })
          .catch((err) => {
            console.log(err)
            setIsAuthenticated(false);
          });
      }
    } finally {
      setIsLoading(false); // <-- clear loading state when completed
    }
  };

  useEffect(() => {
    checkAuth();
  }, []);

  if (isLoading) {
    return <div className="">Loading...</div>;
  }

  return isAuthenticated ? children : <Navigate to={"/login"} />;

}

And here is the code snippet that's called in app.js

<Route
  path="/scroll"
  element={(
    <ProtectedRoute>
      <Scroll /> 
    </ProtectedRoute>
  }
/> 

Solution

  • The axios.post starts an asynchronous Promise chain that the synchronous code around it doesn't wait for. The finally block is executed prior to the POST request resolving.

    The code should await the Promise to resolve.

    export default function ProtectedRoute() {
      const [isAuthenticated, setIsAuthenticated] = useState();
    
      // add loading state, initially true for initial render
      const [isLoading, setIsLoading] = useState(true);
    
      const checkAuth = async () => {
        const cookie = new Cookies(); 
        setIsLoading(true);
    
        try {
          if (cookie.get("stytch_session") == null) {
            console.log("no cookies!")
            setIsAuthenticated(false);
          } else {
            await Axios.post(
              "http://localhost:5001/test",
              {},
              { headers: { sessiontoken: cookie.get("stytch_session") } }
            );
            console.log("reaching private route!")
            setIsAuthenticated(true);
        } catch(err) {
          console.log(err);
          setIsAuthenticated(false);
        } finally {
          setIsLoading(false);
        }
      };
    
      useEffect(() => {
        checkAuth();
      }, []);
    
      if (isLoading) {
        return <div className="">Loading...</div>;
      }
    
      return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
    }
    
    <Route element={<ProtectedRoute />}>
      <Route path="/scroll" element={<Scroll />} />
      ... other protected routes ...
    </Route>