skip to Main Content

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;

2

Answers


  1. 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 />;
    }
    
    Login or Signup to reply.
  2. try this

    import { useLocation, Navigate, Outlet } from "react-router-dom";
    function PrivateRoutes(){
      const {loggedIn} = useAuth();
      return loggedIn ? <Outlet/>: <Navigate to="/" state={{from:location}} replace/>
    
    } 
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search