I'm trying to implement authentication for my React app and got stuck with Protected Routes and Navigation (react-router v6).
Disclaimer: I'm pretty new to many of the aspects involved in authentication and react-router, therefore I must be missing some basic aspect.
I'll try to explain the logic I'm following, and the issue:
I'm using a Protected Route that is only accessible when the "loggedIn" property of my "auth" object is set to true. The "auth" object is being passed to the Protected Route Component via context since it's a state defined in App.
Here is the ProtectedRoutes component:
import React, { useContext } from "react";
import { AppContext } from "../App";
import { Navigate, Outlet } from "react-router-dom";
function ProtectedRoutes() {
const { auth } = useContext(AppContext);
console.log("Auth Route: ", auth.loggedIn); // This always logs Auth Route: undefined (or whatever I initialize the state with)
return auth.loggedIn ? <Outlet /> : <Navigate to="/" />;
}
export default ProtectedRoutes;
Here is a simplified version of the App, where the logged state is stored and the routes defined (just using one children route for simplicity and the states/methods relevant to this issue):
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useState, useEffect, createContext } from "react";
import Home from "./Pages/Home";
import Login from "./Pages/Login";
import ErrorPage from "./Pages/ErrorPage";
import ProtectedRoutes from "./components/ProtectedRoutes";
import Axios from "axios";
export const AppContext = createContext();
function App() {
const [auth, setAuth] = useState({});
console.log("Authentication status: ", auth); // This logs Authentication status: {loggedIn: true, ...} when the user is logged in.
//The use effect below sets the auth state according to info sent by the server.
useEffect(() => {
Axios.get("http://localhost:3001/login").then((response) => {
if (response.data.loggedIn) {
setAuth(response.data);
}
});
}, []);
return (
<div className="App">
<AppContext.Provider value={{ auth, setAuth }}>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route element={<ProtectedRoutes />}>
<Route path="/home" element={<Home />} />
</Route>
<Route path="*" element={<ErrorPage />} />
</Routes>
</Router>
</AppContext.Provider>
</div>
);
}
export default App;
The problem: if I use the Navigate component in my protected route (to redirect in case the user is not logged in), the auth object is always read as undefined (or whatever I initialize it to). A console log in the App gives the updated state though (with loggedIn set to true when logged in). If I replace the Navigate component in the ternary operator with some , the auth object logs just as I expected in the protected rout component:
import React, { useContext } from "react";
import { AppContext } from "../App";
import { Navigate, Outlet } from "react-router-dom";
function ProtectedRoutes() {
const { auth } = useContext(AppContext);
console.log("Auth Route: ", auth.loggedIn); // This logs Auth Route: true when logged in.
return auth.loggedIn ? <Outlet /> : <div>Some text to show if logged out</div<;
}
export default ProtectedRoutes;
I have no idea why it behaves like this, I must be misunderstanding the flow of rendering or any other aspect. Any help would be appreciated :)
I've tried passing the "auth" state down via props but the issue persists, so the problem is not with useContext.