Problem:
Here it’s shown the most basic implementation of authentication in React-Router v6. loggedIn
flag in PrivateRoutes
does not change when updated to true after updating context, and so the private routes cannot be accessed when navigating to them. Why is that?
import './App.css';
import {
BrowserRouter,
Navigate,
Outlet,
Route,
Routes,
useNavigate
} from 'react-router-dom';
import React, { useState } from 'react';
function App() {
return (
<div>
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route element={<PrivateRoutes />}>
<Route path="/secret" element={<SecretPage />} />
{/** ... Other protected pages ...*/}
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
</div>
);
}
function PrivateRoutes() {
const { loggedIn } = useAuth();
return loggedIn ? <Outlet />: <Navigate to="/" />
}
function HomePage() {
const navigate = useNavigate();
return <>
<h1>Home page</h1>
<button onClick={() => navigate('/secret')}>
Go to secret page
</button>
</>
}
function SecretPage() {
return <>
<h1>Secret Page</h1>
</>
}
const AuthContext = React.createContext()
const AuthProvider = ({ children }) => {
const { loggedIn, signIn } = useAuth()
return
<AuthContext.Provider value={loggedIn}>
{/**
* Button to trigger sign in and showing the
* current status.
*/}
<button onClick={signIn}>LogIn</button>
<h2>Logged In: {loggedIn.toString()}</h2>
<p>-----------------------</p>
{children}
</AuthContext.Provider>
}
const useAuth = () => {
const [loggedIn, setLoggedIn] = useState(false)
const signIn = () => {
{/** sign in logic ....*/}
setLoggedIn(true);
};
return { loggedIn, signIn };
}
export default App;
Solution:
useAuth
is a React hook and React hooks don’t share internal state. Each instance of useAuth
has its own independent loggedIn
state.
Refactor the AuthProvider
to hold the loggedIn
state and update the useAuth
hook to consume the provided context value. This is how all instances of useAuth
get the same value.
const AuthContext = React.createContext();
const AuthProvider = ({ children }) => {
const [loggedIn, setLoggedIn] = useState();
const signIn = () => {
{/** sign in logic ....*/}
setLoggedIn(true);
};
return (
<AuthContext.Provider value={{ loggedIn, signIn }}>
{/**
* Button to trigger sign in and showing the
* current status.
*/}
<button onClick={signIn}>LogIn</button>
<h2>Logged In: {loggedIn.toString()}</h2>
<p>-----------------------</p>
{children}
</AuthContext.Provider>
);
}
const useAuth = () => React.useContext(AuthContext);
function PrivateRoutes() {
const { loggedIn } = useAuth();
if (loggedIn === undefined) {
return null; // or loading indicator/spinner/etc
}
return loggedIn
? <Outlet />
: <Navigate to="/" replace />;
}