How to render react routes after auth response?

Viewed 82

When someone arrives to my webpage I first check if the user is authenticated or not. I need to wait for the response from my GET /auth/loggedin request before rendering the routes so that I know if I should redirect to /login or /. I have added conditional rendering to the App.js component, but it always redirects the user to /login even when the user is authenticated.

App.js:

import { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import AuthContext from "./contexts/authContext";
import { apiLoggedIn } from "./api/auth";
import ProtectedRoute from "./components/ProtectedRoute";
import Document from "./pages/Document";
import { Login } from "./pages/Login";
import { Signup } from "./pages/Signup";
import Layout from "./components/Layout";
import PublicRoute from "./components/PublicRoute";

export const App = () => {
  const [auth, setAuth] = useState({ isAuth: undefined, user: undefined });
  useEffect(() => {
    apiLoggedIn()
      .then((res) => {
        setAuth({ isAuth: true, user: res.data.userData });
      })
      .catch((err) => {
        if (err.response) {
          setAuth({ isAuth: false, user: {} });
        } else if (err.request) {
          setAuth({ isAuth: false, user: {} });
          console.log(err.request);
        } else {
          setAuth({ isAuth: false, user: {} });
          console.log(err.message);
        }
      });
  }, []);
  return (
    <>
      {auth.isAuth !== undefined ? (
        <AuthContext.Provider value={{ auth, setAuth }}>
          <BrowserRouter>
            <Routes>
              <Route
                path="/"
                element={
                  <ProtectedRoute redirectTo="/login">
                    <Layout />
                  </ProtectedRoute>
                }
              >
                <Route index element={<Document />} />
              </Route>
              <Route
                path="/signup"
                element={
                  <PublicRoute>
                    <Signup />
                  </PublicRoute>
                }
              />
              <Route
                path="/login"
                element={
                  <PublicRoute>
                    <Login />
                  </PublicRoute>
                }
              />
            </Routes>
          </BrowserRouter>
        </AuthContext.Provider>
      ) : (
        ""
      )}
    </>
  );
};

./api/auth.js

import axios from "axios";

export const apiLoggedIn = () => {
  return axios.get("/auth/loggedin");
};

./components/ProtectedRoute.js

import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";

const ProtectedRoute = ({ children, redirectTo }) => {
  const { isAuth } = useContext(AuthContext);
  return isAuth ? children : <Navigate to={redirectTo} />;
};

export default ProtectedRoute;

./components/PublicRoute.js

import { useContext } from "react";
import AuthContext from "../contexts/authContext";
import { Navigate } from "react-router-dom";

const PublicRoute = ({ children }) => {
  const { isAuth } = useContext(AuthContext);
  return isAuth ? <Navigate to={"/"} /> : children;
};

export default PublicRoute;

./contexts/authContext.js

import { createContext } from "react";

const AuthContext = createContext({
  auth: { isAuth: false, user: {} },
  setAuth: () => {},
});

export default AuthContext;
3 Answers

The AuthContext value is an object with auth and setAuth properties

<AuthContext.Provider value={{ auth, setAuth }}>
  ...
</AuthContext.Provider>

But in the route wrapper you are referencing an isAuth property, which will always be undefined, i.e. falsey.

const ProtectedRoute = ({ children, redirectTo }) => {
  const { isAuth } = useContext(AuthContext);
  return isAuth ? children : <Navigate to={redirectTo} />;
};

This is why the redirect always occurs. To resolve, ensure you reference the same context values throughout the code.

Either specify an isAuth context value:

<AuthContext.Provider value={{ isAuth: auth.isAuth, setAuth }}>
  ...
</AuthContext.Provider>

Or fix the wrappers:

const ProtectedRoute = ({ children, redirectTo }) => {
  const { auth } = useContext(AuthContext);
  return auth.isAuth ? children : <Navigate to={redirectTo} />;
};

...

const PublicRoute = ({ children }) => {
  const { auth } = useContext(AuthContext);
  return auth.isAuth ? <Navigate to={"/"} /> : children;
};

the key name is not passed correctly in auth provider it should be passed as following here is sample sandbox demo : https://codesandbox.io/s/routes-4c5oh?file=/src/App.js

<AuthContext.Provider value={{ isAuth:auth, setAuth }}>

or change the key to auth in each place. in Public and Protected routes like following

const { auth:{isAuth} } = useContext(AuthContext);

Your context has the following shape :

{
  "auth": {
    "isAuth": true,
    "user": {...},
  },
  "setAuth": function ...
}

you must update ProtectedRoute to

const ProtectedRoute = ({ children, redirectTo }) => {
  const { auth } = useContext(AuthContext);
  return auth.isAuth ? children : <Navigate to={redirectTo} />;
};

you must update PublicRoute to

const PublicRoute = ({ children }) => {
  const { auth } = useContext(AuthContext);
  return auth.isAuth ? <Navigate to={"/"} /> : children;
};

Related