Search code examples
react-routerreact-componentnested-routes

Nesting React routes to login-protected pages


I am using [email protected]

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')
);

Solution

  • 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>