1

I am using react-router-dom@6.3.0

I have created a React app where certain Private pages are accessible only users who have logged in.

You can find a demo here, and a GitHub repo here.

A simplified version of this is shown below.

I have wrapped every Private page in its own RequireLogin component, and this works:

          <Route
            path="/private1"
            element={
              <RequireLogin redirectTo="/">
                <Private
                  text="Private Page #1"
                />
              </RequireLogin >
            }
          />

The RequireLogin component redirects to a page with the Login component if the user is not logged in, and renders the requested component only to a logged in user.

My question is this:

Is it there a way to wrap all the Private routes inside one RequireLogin component, or do I have to wrap each one separately?


import React, { createContext, useContext, useState } from 'react'
import ReactDOM from 'react-dom';
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Navigate,
  NavLink
} from "react-router-dom";



const UserContext = createContext()

const UserProvider = ({children}) => {
  const [ loggedIn, logIn ] = useState("")

  return (
    <UserContext.Provider
      value={{
        loggedIn,
        logIn
      }}
    >
      {children}
    </UserContext.Provider>
  )
 }



function App() {
  return (
    <Router>
      <UserProvider>
        <Routes>
          <Route
            path="/"
            element={<NavLink to="/login">Log In</NavLink>}
          />

          <Route
            path="/login"
            element={<Login />}
          />

          <Route
            path="/private1"
            element={
              <RequireLogin redirectTo="/login">
                <Private
                  text="Private Page #1"
                />
              </RequireLogin >
            }
          />

          <Route
            path="/private2"
            element={
              <RequireLogin redirectTo="/login">
                <Private
                  text="Private Page #2"
                />
              </RequireLogin >
            }
          />
        </Routes>
      </UserProvider>
    </Router>
  );
}



function Menu({hideLogOut}) {
  const { loggedIn } = useContext(UserContext)

  if (loggedIn) {
    if (!hideLogOut) {
      return <ul>
        <li><NavLink to="/login">Log Out</NavLink></li>
        <li><NavLink to="/private1">Private #1</NavLink></li>
        <li><NavLink to="/private2">Private #2</NavLink></li>
      </ul>
    } else {
      return <ul>
        <li><NavLink to="/private1">Private #1</NavLink></li>
        <li><NavLink to="/private2">Private #2</NavLink></li>
      </ul>
    }
  } else {
    return <p>Not Logged In</p>
  }
}



function RequireLogin ({ children, redirectTo }) {
  const { loggedIn } = useContext(UserContext);
     
  return loggedIn
       ? children
       : <Navigate to={redirectTo} />;
}


function Private({text}) {
  return (
    <div>
      <Menu />
      <h1>{text}</h1>
    </div>
  )
}


function Login() {
  const { loggedIn, logIn } = useContext(UserContext)

  const toggleLogged = () => {
    logIn(!loggedIn)
  }

  return (<div>
    <Menu
      hideLogOut={true}
    />
    <label htmlFor="loggedIn">   
      <input
        type="checkbox"
        name="loggedIn"
        id="loggedIn"
        checked={loggedIn}
        onChange={toggleLogged}
      />
      Pretend that we are logged in
    </label>
  </div>)
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
James Newton
  • 6,623
  • 8
  • 49
  • 113
  • Yes, there is. The `RequireLogin` component should render an `Outlet` for nested `Route` components to render their content into instead of using the `children` prop for a single route. – Drew Reese Jun 13 '22 at 21:31

1 Answers1

1

I use a second router for the private routes, wrapped with a single <RequireLogin>. Example:

    <Routes>
      <Route path="/login" element={<LoginPage />} />
      <Route path="/register" element={<RegistrationPage />} />
      <Route path="*" element={
        <RequireLogin>
          <Routes>
            <Route path="/" element={<FeedPage />} />
            <Route path="/explore" element={<ExplorePage />} />
            <Route path="/user/:username" element={<UserPage />} />
            <Route path="*" element={<Navigate to="/" />} />
          </Routes>
        </RequireLogin>
      } />
    </Routes>
Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152