125

I'm using React Router v6 and am creating private routes for my application.

In file PrivateRoute.js, I've the code

import React from 'react';
import {Route,Navigate} from "react-router-dom";
import {isauth}  from 'auth'

function PrivateRoute({ element, path }) {
  const authed = isauth() // isauth() returns true or false based on localStorage
  const ele = authed === true ? element : <Navigate to="/Home"  />;
  return <Route path={path} element={ele} />;
}

export default PrivateRoute

And in file route.js I've written as:

 ...
<PrivateRoute exact path="/" element={<Dashboard/>}/>
<Route exact path="/home" element={<Home/>}/>

I've gone through the same example React-router Auth Example - StackBlitz, file App.tsx

Is there something I'm missing?

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Because i hate myself
  • 1,469
  • 2
  • 4
  • 11

15 Answers15

132

I ran into the same issue today and came up with the following solution based on this very helpful article by Andrew Luca

In PrivateRoute.js:

import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';

const PrivateRoute = () => {
    const auth = null; // determine if authorized, from context or however you're doing it

    // If authorized, return an outlet that will render child elements
    // If not, return element that will navigate to login page
    return auth ? <Outlet /> : <Navigate to="/login" />;
}

In App.js (I've left in some other pages as examples):

import './App.css';
import React, {Fragment} from 'react';
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Home from './components/pages/Home';
import Register from './components/auth/Register'
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';

const App = () => {
  return (
    <Router>
      <Fragment>
        <Navbar/>
        <Routes>
          <Route exact path='/' element={<PrivateRoute/>}>
            <Route exact path='/' element={<Home/>}/>
          </Route>
          <Route exact path='/register' element={<Register/>}/>
          <Route exact path='/login' element={<Login/>}/>
        </Routes>
      </Fragment>
    </Router>
    
  );
}

In the above routing, this is the private route:

<Route exact path='/' element={<PrivateRoute/>}>
      <Route exact path='/' element={<Home/>}/>
</Route>

If authorization is successful, the element will show. Otherwise, it will navigate to the login page.

Dallin Romney
  • 1,446
  • 1
  • 5
  • 4
  • 2
    Ah, I read that blog and it makes much more sense now to simply render `} />` as the child of the private outlet. This is a bit more appealing now. I can see the benefits in certain use cases. – Drew Reese Nov 07 '21 at 06:56
  • 10
    If using latest version, `exact` attribute not required. – Suroor Ahmmad Nov 11 '21 at 18:09
  • 1
    @DrewReese is there some sort of built in Auth component? I'm no expert in React so I'm curious to see other solutions. In my understanding the PrivateRoute component in this example acts as an Auth component – Dallin Romney Nov 12 '21 at 21:11
  • 1
    @DrewReese just realized you're talking about the additional route inside the route. If Home was the only page requiring authorization, then you're correct, the child route isn't necessary. If several routes require the same authentication, then putting them all within the same PrivateRoute is convenient. – Dallin Romney Nov 12 '21 at 21:16
  • 2
    @DallinRomney In React, no, React alone is rather unopinionated when it comes to auth implementations, but any libraries used can certainly provide their own auth containers/wrapper. And that is the appealing part. :) It's effectively the same pattern I used in v5 and what I called a "Golden Gate" route that was a singular point of auth access into a "Walled Garden" of routes that each individually didn't need the auth check now. – Drew Reese Nov 12 '21 at 21:20
  • I think I'm a little bit late but in case it's worth and someone needs it the documentation it's really helpful https://reactrouter.com/docs/en/v6/getting-started/concepts#layout-routes – Julian Mendez Apr 08 '22 at 04:21
43

Only Route components can be a child of Routes. If you follow the v6 docs then you'll see the authentication pattern is to use a wrapper component to handle the authentication check and redirect.

function RequireAuth({ children }: { children: JSX.Element }) {
  let auth = useAuth();
  let location = useLocation();

  if (!auth.user) {
    // Redirect them to the /login page, but save the current location they were
    // trying to go to when they were redirected. This allows us to send them
    // along to that page after they login, which is a nicer user experience
    // than dropping them off on the home page.
    return <Navigate to="/login" state={{ from: location }} />;
  }

  return children;
}

...

<Route
  path="/protected"
  element={
    <RequireAuth>
      <ProtectedPage />
    </RequireAuth>
  }
/>

The old v5 pattern of create custom Route components no longer works. An updated v6 pattern using your code/logic could look as follows:

const PrivateRoute = ({ children }) => {
  const authed = isauth() // isauth() returns true or false based on localStorage
  
  return authed ? children : <Navigate to="/Home" />;
}

And to use

<Route
  path="/dashboard"
  element={
    <PrivateRoute>
      <Dashboard />
    </PrivateRoute>
  }
/>
Drew Reese
  • 103,803
  • 12
  • 69
  • 96
  • 3
    Dallin's answer is nice, but to be honest doesn't save you much of anything over just wrapping the target component with an auth component. If anything it's a more complex solution since it now involves rendering ***two*** `Route` components ***and*** an `Outlet` just to render a single path. – Drew Reese Nov 07 '21 at 06:49
  • 2
    Thank you this was my solution and it worked. – Because i hate myself Nov 07 '21 at 13:44
  • @DrewReese, I think the advantage of Dallin's answer comes into play when you have many routes that need to be protected. So with only one protected route, yeah it doesn't really make any sense, but what about 5 or 20? It starts to save on not requiring a bunch of redundant wrapper components. – MikeyT Jan 06 '22 at 07:38
  • @MikeyT Totally agree, see comments under his answer. Mine was initially essentially taken straight from the official docs, for protecting a single component. When addressing these sorts of questions I typically cover both use cases now. – Drew Reese Jan 06 '22 at 16:17
17

Complement to reduce lines of code, make it more readable and beautiful.

This could just be a comment but I don't have enough points, so I'll put it as an answer.

Dallin's answer works but Drew's answer is better! And just to complete Drew's answer on aesthetics, I recommend creating a private component that takes components as props instead of children.

Very basic example of private routes file/component:

import { Navigate } from 'react-router-dom';

const Private = (Component) => {
    const auth = false; //your logic

    return auth ? <Component /> : <Navigate to="/login" />
}

Route file example:

<Routes>
    <Route path="/home" element={<Home />} />
    <Route path="/user" element={<Private Component={User} />} />
</Routes>
Dhenyson Jhean
  • 195
  • 2
  • 7
7

I know that this is not exactly the recipe how to make PirvateRoute work, but just wanted to mention that new docs recommend a slightly different approach to handle this pattern with react-router v6:

    <Route path="/protected" element={<RequireAuth><ProtectedPage /></RequireAuth>} />
import { Navigate, useLocation } from "react-router";

export const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
  let auth = useAuth();
  let location = useLocation();

  if (!auth.user) {
    return <Navigate to="/login" state={{ from: location }} />;
  }

  return children;
};

and you are supposed to add more routes inside ProtectedPage itself if you need it.

See docs and example for more details. Also, check this note by Michael Jackson that goes into some implementation details.

JLarky
  • 9,125
  • 3
  • 33
  • 28
7

Just set your router component to element prop:

<Routes>
  <Route exact path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/dashboard" element={<Dashboard />} />
</Routes>

You can also check for upgrading from v5, https://reactrouter.com/docs/en/v6/upgrading/v5

cansu
  • 994
  • 7
  • 20
3

React Router v6, some syntactic sugar:

{auth && (
  privateRoutes.map(route =>
    <Route
      path={route.path}
      key={route.path}
      element={auth.isAuthenticated ? <route.component /> : <Navigate to={ROUTE_WELCOME_PAGE} replace />}
    />
  )
)}
Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Cyclion
  • 668
  • 8
  • 9
  • 4
    Syntactic sugar in what way? How does it solve the problem? An explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/70016325/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jan 05 '22 at 02:53
2

I tried all answers but it always display the error:

Error: [PrivateRoute] is not a component. All component children of must be a or <React.Fragment>

But i found a solution )))

In PrivateRoute.js file:

import React from "react"; import { Navigate } from "react-router-dom"; 
import {isauth}  from 'auth' 

const PrivateRoute= ({ children }) => {
 const authed = isauth()

  return authed  ? children : <Navigate to={"/Home" /> };

export default ProtectedRoute;

In route.js file:

<Route
  path="/"
  element={
      <ProtectedRoute >
        <Dashboard/>
      </ProtectedRoute>
    }
/>
<Route exact path="/home" element={<Home/>}/>
1

If you are using a second file to work on your index.js . Here is a more simple code snippet that may help others understand better. It is pretty straight forward and you do not require "exact" and "switch" to change between the paths thanks to the new version.

index.js

import React from "react";
import ReactDOM from "react-dom";

import { BrowserRouter } from "react-router-dom";

import "./index.css";
import App from "./App";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

App.js

Note that you need to import { Routes, Route } instead of { Route } (as it was in previous versions). Also, note that on the App.js file, it is not necessary to import BrowserRouter again.

import { Routes, Route } from "react-router-dom";

import AllPages from "./pages/AllPages";
import NewContactsPage from "./pages/ContactsPage";
import FavoritesPage from "./pages/Favorites";

function App() {

  return (
    <div>
      <Routes>
        <Route path="/" element={<AllPages />} />
        <Route path="/new-contacts" element={<NewContactsPage />} />
        <Route path="/favorites" element={<FavoritesPage />} />
      </Routes>
    </div>
  );
}

export default App;
mapa815
  • 71
  • 5
1

This is the simple way to create private route

import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext'

export default function PrivateRoute({ children }) {
  const { currentUser } = useAuth()

  if (!currentUser) {
    return <Navigate to='/login' />
  }

  return children;
}

Now if we want to add private route to Dashboard component we can apply this private route as below

<Routes>
  <Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
Roman Mahotskyi
  • 2,344
  • 2
  • 22
  • 44
0

For longer elements

        <Router>
        <div>
            <Navbar totalItems={cart.total_items}/>
            <Routes>
                <Route exact path='/'>
                    <Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart}/>}/>
                </Route>
                <Route exact path='/cart'>
                    <Route exact path='/cart' element={<Cart cart={cart}/>}/>     
                </Route>
            </Routes>
        </div>
    </Router>
Romil Jain
  • 167
  • 1
  • 5
  • 2
    An explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/70126878/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jan 05 '22 at 02:54
  • 1
    See "[Explaining entirely code-based answers](https://meta.stackoverflow.com/q/392712/128421)". While this might be technically correct it doesn't explain why it solves the problem or should be the selected answer. We should educate in addition to help solve the problem. – the Tin Man Jan 22 '22 at 08:56
0

Header will stay on all page

import React from 'react';

import {
  BrowserRouter,
  Routes,
  Route
} from "react-router-dom";

const Header = () => <h2>Header</h2>
const Dashboard = () => <h2>Dashboard</h2>
const SurveyNew = () => <h2>SurveyNew</h2>
const Landing = () => <h2>Landing</h2>


const App = () =>{
  return (
    <div>
      <BrowserRouter>
        <Header />
        <Routes >
        <Route exact path="/" element={<Landing />} />
        <Route path="/surveys" element={<Dashboard />}  />
        <Route path="/surveys/new" element={<SurveyNew/>}  />
        </Routes>
      </BrowserRouter>
    </div>
  );
};
export default App;
Rafale jam
  • 11
  • 1
  • An explanation would be in order. E.g., what is the idea/gist? What do you mean by *"Header will stay on all page"*? Do you mean *"Header will stay on all pages"*? Or something else? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/70250323/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jan 05 '22 at 02:55
  • Text header is fixed on all page you can put navbar or etc.. – Rafale jam Jan 10 '22 at 20:41
0

you can use function for private route

<Route exact path="/login" element={NotAuth(Login)} />
<Route exact path="/Register" element={NotAuth(Register)} />

function NotAuth(Component) {
  if (isAuth) return <Navigate to="/" />;
  return <Component />; 
}
Abdulaziz Noor
  • 6,251
  • 2
  • 18
  • 21
0

Remove the PrivateRoute component from your project and use the following code in your App.js files:

import {Navigate} from "react-router-dom";
import {isauth}  from 'auth'

...

<Route exact path="/home" element={<Home/>}/>
<Route exact path="/" element={isauth ? <Dashboard/> : <Navigate to="/Home"  />}/>
Noomen
  • 23
  • 4
-1

for the Error "[Navigate] is not a component. All component children of must be a or <React.Fragment>"

use the following method maybe solved:

DefaultPage is when no match router, jump to the DefaultPage here use the <Route index element={} /> to replace the

<Navigate to={window.location.pathname + '/kanban'}/>

here is the link: https://reactrouter.com/docs/en/v6/getting-started/tutorial#index-routes

}/> }/> {/**/} } />
Leo
  • 19
  • 1
-4
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

function App() {
  return (
      <Router>
          <Routes>
            <Route path="/" element={<h1>home page</h1>} />
            <Route path="/seacrch" element={<h1>seacrch page</h1>} />
          </Routes>
      </Router>
  );
}

export default App;
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 03 '21 at 08:13
  • An explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/70210694/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jan 05 '22 at 02:55
  • 1
    It doesn't answer the question. – Xavier Lambros Jan 27 '22 at 21:35