194

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,738
  • 21
  • 105
  • 131
Rajanboy
  • 2,266
  • 2
  • 8
  • 15

18 Answers18

211

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
  • 2,276
  • 1
  • 6
  • 5
  • 3
    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
  • 19
    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
  • If you know how to do this with the new `createBroswerRouter` function, I'd be interested to know the anser in my question https://stackoverflow.com/questions/74649974/private-route-with-createbrowserrouter (or as a comment here). – saner Dec 02 '22 at 01:36
  • I was using `createBrowserRouter` and is almost the same. The only difference is that I had problems with the `loader` function because I had inside other function that automatically renews a JWT token so the view only show the error page (*no redirect executed*). So, if you have a loader that can throw any error if the user is not authenticated (a valid token in my case) try catch it and return `null` or something. – StandardIO Apr 25 '23 at 22:52
  • seems to work perfectly using react router v6. And the boilerplate code for specifying the private route has also changed in react router v6. Many thanks for this. Really helpful. – codemt Jul 24 '23 at 09:32
71

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
  • 165,259
  • 14
  • 153
  • 181
  • 7
    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. – Rajanboy 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
  • Why wrap individual routes with `` ? Why not wrap an entire `` with it ? Seems like a better option right ? – DollarAkshay Nov 18 '22 at 16:21
  • 1
    @DollarAkshay "Better" is subjective, but to answer your question, perhaps it's more granular control. At the time when I answered this here this was the pattern used in the RRD docs. Perhaps I should have included a link to their example/sandbox. I prefer the "wrap a set of routes" approach as in Dallin's answer or my other answer [here](/a/66289280/8690857). – Drew Reese Nov 18 '22 at 16:24
38

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
  • 634
  • 6
  • 14
  • 10
    It worked for me, I just had to change `const Private = (Component) =>` TO const `Private = ({Component}) =>` – Daniel Miranda Jan 27 '22 at 02:36
  • 1
    What happens when you want to pass props to `Component`? You *could* make it take JSX like the `element` prop, i.e. `Component={}` but you'd basically be back where you started with the `element` prop and you'd be better to just wrap `` with the `Private` component. The entire point of the `element` JSX syntax of the `Route` is the route doesn't need to concern itself is passing anything to the component it is rendering. – Drew Reese Sep 13 '22 at 23:24
  • @DrewReese Then, should we use this approach or your approach? – Jack Mar 07 '23 at 14:43
  • @fredrick I would suggest Dallin's answer as it allows you to wrap entire groups of routes that need to be protected. It's a more ***DRY*** version of my answer which only wraps individual components. – Drew Reese Mar 07 '23 at 16:45
  • @DrewReese Thanks for reply. What about Dhenyson's answer that also seems to be clean and working? What is the problem with this one or any cons? – Jack Mar 07 '23 at 16:49
  • @fredrick This answer breaks down as soon as you need to also pass props to `Component`. You either need to pass a props object through to then be passed into `Component`, or you can just pass `` which as I said is basically just back to what the `element` prop does in the first place. – Drew Reese Mar 07 '23 at 16:54
  • Actually I am trying to apply the solutions to nested routes, but could not. Would you mind adding an update to your question to show how to apply Dallin's approach to nested routes? It would really be useful. – Jack Mar 07 '23 at 17:15
  • And I would really be appreciated as it would really be so useful for me :) I hope you could post a wonderful example. Thanks in advance. – Jack Mar 07 '23 at 20:00
10

I know that this is not exactly the recipe on how to make PirvateRoute work, but I just wanted to mention that the new documentation recommends 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 the documentation and an example for more details. Also, check this note by Michael Jackson that goes into some implementation details.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JLarky
  • 9,833
  • 5
  • 36
  • 37
7

It's 2022 and I did something like below:

// routes.tsx

import { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import Private from "./Private";
import Public from "./Public";

const Home = lazy(() => import("../pages/Home/Home"));
const Signin = lazy(() => import("../pages/Signin/Signin"));

export const Router = () => {
  return (
    <Routes>
      <Route path="/" element={Private(<Home />)} />
      <Route path="/signin" element={Public(<Signin />)} />
    </Routes>
  );
};
// Private.tsx

import { Navigate } from "react-router-dom";
import { useEffect, useState } from "react";

function render(c: JSX.Element) {
  return c;
}

const Private = (Component: JSX.Element) => {
  const [hasSession, setHasSession] = useState<boolean>(false);

  useEffect(() => {
    (async function () {
      const sessionStatus = await checkLoginSession();

      setHasSession(Boolean(sessionStatus));
    })();
  }, [hasSession, Component]);


  return hasSession ? render(Component) : <Navigate to="signin" />;
};

export default Private;

Hope this helps!

filoscoder
  • 495
  • 1
  • 4
  • 10
  • 1
    one of the cleanest by far, Public(...) function should be removed and just use the component as element – Facundo Colombier Nov 29 '22 at 16:21
  • 2
    You're partially right, the Public() renderer is needed if we want to provide redirection for authenticated users. If "A" user is already signed in and authenticated session exists is very awkward letting show public pages like "Signin" or "Signup". – filoscoder Dec 01 '22 at 07:05
3

Just set your router component to the 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.

Srdjan Pazin
  • 103
  • 2
  • 5
cansu
  • 958
  • 1
  • 12
  • 23
3

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
  • 53
  • 4
2

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,738
  • 21
  • 105
  • 131
Cyclion
  • 738
  • 9
  • 9
  • 5
    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 displayed 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 the route.js file:

<Route
  path="/"
  element={
    <ProtectedRoute >
      <Dashboard/>
    </ProtectedRoute>
  }
/>
<Route exact path="/home" element={<Home/>}/>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

This is the simple way to create a 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 a private route to the Dashboard component we can apply this private route as below:

<Routes>
  <Route exact path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
smit agravat
  • 223
  • 2
  • 9
2

Children of Routes need to be Route elements, so we can change the ProtectedRoute:

export type ProtectedRouteProps = {
    isAuth: boolean;
    authPath: string;
    outlet: JSX.Element;
};

export default function ProtectedRoute({
    isAuth,
    authPath,
    outlet,
}: ProtectedRouteProps) {
    if (isAuth) {
        return outlet;
    } else {
        return <Navigate to={{pathname: authPath}} />;
    }
}

And then use it like this:

const defaultProps: Omit<ProtectedRouteProps, 'outlet'> = {
  isAuth: //check if user is authenticated,
  authPath: '/login',
};

return (
  <div>
    <Routes>
        <Route path="/" element={<ProtectedRoute {...defaultProps} outlet={<HomePage />} />} />
    </Routes>
  </div>
);
GDBxNS
  • 139
  • 6
-1

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
  • 305
  • 3
  • 5
  • 2
    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
-1

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;
-1

You can use a function for a 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 />;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Abdulaziz Noor
  • 6,413
  • 2
  • 19
  • 21
-1
<Route path='/' element={<Navigate to="/search" />} />
arsi
  • 53
  • 4
-1

I'm using "react-router-dom": "^6.3.0" and this is how I did mine

PrivateRoute Component and Route

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

    const PrivateRoute = ({ component: Compontent, authenticated }) => {
      return authenticated ? <Compontent /> : <Navigate to="/" />;
    }
    
    <Route 
          path="/user/profile" 
          element={<PrivateRoute authenticated={true} component={Profile} />} />
Koala
  • 352
  • 2
  • 7
  • 18
-2

For the error "[Navigate] is not a <Route> component. All component children of <Routes> must be a <Route> 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'}/>

See Index Routes

<Routes>

      <Route path={'/default'} element={<DefaultPage/>}/>

      <Route path={'/second'}  element={<SecondPage/>}/>

{/* <Navigate to={window.location.pathname + '/kanban'}/> */}
      <Route index element={<DefaultPage/>} />

</Routes>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Leo
  • 17
  • 1
  • Even after all the unhiding, etc. this is practically incomprehensible. Do you use [machine translation](https://en.wikipedia.org/wiki/Machine_translation)? Can you [fix](https://stackoverflow.com/posts/71308352/edit) it? – Peter Mortensen Aug 17 '22 at 14:53
-5
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
  • 2
    It doesn't answer the question. – Xavier Lambros Jan 27 '22 at 21:35