Search code examples
javascriptreactjsauthenticationreact-router-dom

React application shows the homepage briefly, then redirects to the login page if the user is not authenticated


I am using React.js and react-router-dom version 6. I have set up a private Outlet and made redirecting functionality works.

The problem is if the user is not authenticated and tries to go to the homepage, React shows the homepage but immediately sends the user to the login page. Everything happens in a second. I don't want to show the homepage.

// private Outlet

import axios from "axios";
import { useEffect, useState } from "react";
import { Navigate, Outlet } from "react-router-dom";

export default function PrivateOutlet() {
  const [user, setUser] = useState(true);
  const [isLoading, setIsLoading] = useState(true);

  const getUser = async () => {
    try {
      const { data } = await axios.get(
        `${process.env.REACT_APP_API_URL}auth/login/success`,
        {
          withCredentials: true,
        }
      );
      console.log(data.isLoggedIn);
      setUser(data);
    } catch (error) {
      setUser(false);
    }
  };
  useEffect(() => {
    getUser();
    setIsLoading(false);
  }, []);

  if (isLoading) return <h1>loading</h1>;

  return user ? <Outlet /> : <Navigate to="/login" />;
}

// APP.js

import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import PrivateOutlet from "./components/PrivateOutlet";

function App() {
  return (
    <BrowserRouter>
      <div>
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route element={<PrivateOutlet />}>
            <Route path="/" element={<Home />} />
          </Route>
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;


Solution

  • In your useEffect, you are calling setIsLoading(false) just after getUser(), so the loading state is changed before the API call completes. You could move setIsLoading(false) inside getUser in a finally block, like so:

    import axios from "axios";
    import { useEffect, useState } from "react";
    import { Navigate, Outlet } from "react-router-dom";
    
    export default function PrivateOutlet() {
      // set the user to null at first
      const [user, setUser] = useState(null);
      const [isLoading, setIsLoading] = useState(true);
    
      useEffect(() => {
        const getUser = async () => {
          try {
            const { data } = await axios.get(`${process.env.REACT_APP_API_URL}auth/login/success`, {
              withCredentials: true,
            });
            console.log(data.isLoggedIn);
            setUser(data);
          } catch (error) {
            // you can set some error message state here and show it to the user if you want.
            console.log(error);
          } finally {
            setIsLoading(false);
          }
        };
    
        getUser();
      }, []);
    
      if (isLoading) return <h1>loading</h1>;
    
      return user ? <Outlet /> : <Navigate to="/login" />;
    }
    

    Notice I also changed the initial value of the user state to null, and changing this value only when the request is a success.