DEV Community

Cover image for πŸ” Private Route in React Router v6
Andrei Luca
Andrei Luca

Posted on • Updated on

πŸ” Private Route in React Router v6

Things are changing fast in WEB today, and react-router v6 is in beta already and around the corner. πŸ€”

This is just for learning purposes only, react-router v6 is still in beta, use at your own risk

Private routes in v5 and below were done in a specific way using a custom component mostly named PrivateRoute that was most of the times just a wrapper and composition of basic Route and Redirect e.g.

function PrivateRoute(props) {
  let { component: Component, children, render, ...rest } = props
  let auth = useAuth();
  return (
    <Route
      {...rest}
      render={() => auth
        ? <Component />
        : <Redirect to="/login" />
      }
    />
  );
}

function App() {
  return (
    <BrowserRouter>
      <Route path="/" component={Public} />
      <PrivateRoute path="/private" component={Private} />
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

But taking a look at v6 docs it seems that things changed a little bit, and we need to think a little bit different about it.

For info about all API reference see the link

Let's move on.

Some things that we used to create PrivateRoute have changed a little bit

  • Redirect is now Navigate
  • Route props changed and is just a stub component now
  • A new component Routes appearead

In v6, routes are rendered in such a manner

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Public />} />
        <Route path="/private" element={<Private />} />
      </Routes>
    </BrowserRouter>
  );
}

const Public = () => <div>public</div>;
const Private = () => <div>private</div>;
Enter fullscreen mode Exit fullscreen mode

So as you can see, no more render props or component prop.
You need to pass a direct JSX element (don't worry about performance if you do)

Ok now let's take a look at Route component source code

/**
 * Declares an element that should be rendered at a certain URL path.
 *
 * @see https://reactrouter.com/api/Route
 */
export function Route(_props: RouteProps): React.ReactElement | null {
  invariant(
    false,
    `A <Route> is only ever to be used as the child of <Routes> element, ` +
      `never rendered directly. Please wrap your <Route> in a <Routes>.`
  );
}
Enter fullscreen mode Exit fullscreen mode

wait a minute

Wait a minute where is the code? πŸ‘€ Well actually the parent component Routes will use the Route just as a host for the props and children, and do nothing more with the Route

For more info about Routes implementation see link

So how we do implement our PrivateRoute now? πŸ€” If we do some adjustments to PrivateRoute props, it will look like this

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Public />} />
        <PrivateRoute path="/private" element={<Private />} />
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

But this will not work. Routes will just take the props of PrivateRoute and ignore it's body totally. Even a console.log inside PrivateRoute will not be shown.

So what we do? πŸ€” We do some more adjustments to PrivateRoute

function PrivateRoute({ children }) {
  const auth = useAuth();
  return auth ? <>{children}</> : <Navigate to="/login" />;
}
Enter fullscreen mode Exit fullscreen mode

As you can see we changed Redirect to Navigate, and just return children if user is authenticated. And the usage of it also changes a little bit

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Public />} />
        <Route
          path="/private"
          element={
            <PrivateRoute>
              <Private />
            </PrivateRoute>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you can see PrivateRoute also moves to element prop.

The implementation of PrivateRoute can be done in multiple ways.

Here is a different implementation of PrivateRoute using Outlet

function PrivateOutlet() {
  const auth = useAuth();
  return auth ? <Outlet /> : <Navigate to="/login" />;
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/private-outlet" element={<PrivateOutlet />}>
          <Route element={<Private />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

The pros of this is that you can put multiple private sub routes under same route.

For a full example see this Codesandbox

That's all for today. Happy coding! πŸŽ‰ 🎊 ✨

Keep your users secure!

secure

Cover Photo by Maxim Zhgulev on Unsplash

Oldest comments (61)

Collapse
 
vineyrawat profile image
Viney Rawat

ΰ€Άΰ€Ύΰ€¨ΰ€¦ΰ€Ύΰ€° ΰ€²ΰ₯‡ΰ€– ΰ€­ΰ€Ύΰ€ˆ

Collapse
 
urafiz profile image
UraFIZ

Whan can we use it in create-react-app?

Collapse
 
iamandrewluca profile image
Andrei Luca • Edited

Whenever you want. CRA does not have a router installed by default. But keep in mind it is in beta.

Collapse
 
urafiz profile image
UraFIZ

Bro, you are mistaken. I gave it a try to use all the new features of v6 and didn't succeed coz something under the hood used a history.push.

Thread Thread
 
iamandrewluca profile image
Andrei Luca • Edited

That CRA does not use a router by default I'm very sure. It depends what version of router do you have installed at the moment.

Thread Thread
 
iamandrewluca profile image
Andrei Luca

See codesandbox liinks at the end. In that examples I'm using CRA

Collapse
 
haiderali22 profile image
haiderali22

How can I use this with useRoutes hook provided in react router v6

Collapse
 
iamandrewluca profile image
Andrei Luca
useRoutes([
  {
    path: "/private",
    element: (
      <PrivateRoute>
        <Private />
      </PrivateRoute>
    ),
  },
]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
arknahian profile image
Al Nahian Ark

Ok, but how can I set a state and redirect the users from there they came from

Collapse
 
iamandrewluca profile image
Andrei Luca

You put some state in Navigation props, and on login after submit redirect back, something like this

function PrivateRoute() {
  const location = useLocation()
  return <Navigation to="/login" state={{ from: location.pathname }} />
}

function Login() {
  const navigate = useNavigate()
  const location = useLocation()

  const onSubmit = () => fetch("/api/login").then(() => navigate(location.state.from))

  return <form onSubmit={onSubmit} />
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
Sloan, the sloth mascot
Comment deleted
 
iamandrewluca profile image
Andrei Luca

You mean to prevent before user is clicking a link? πŸ€” Didn't quite get what exactly do you mean.

Collapse
 
ian_jones_c4d87258f948f0d profile image
Ian Jones

Your Outlet example doesn't work with react-router-dom v6.0.2 . Any idea why?

Collapse
 
ian_jones_c4d87258f948f0d profile image
Ian Jones

Appears to have broken in react-router-dom 6.0.0-beta.6

Collapse
 
iamandrewluca profile image
Andrei Luca

Updated react-router-dom to last version and updated example.

It seems that you need to pass path to nested routes also, it wasn't the case in earlier versions

it was

<Route path="/private-outlet" element={<PrivateOutlet />}>
  <Route element={<Private />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

now

<Route path="/private-outlet" element={<PrivateOutlet />}>
  <Route path="" element={<Private />} />
</Route>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aaron__ahn profile image
Aaron Ahn

It worked! Thank you so much :D

Collapse
 
aspyryan profile image
Aspyryan

I was using a module to route users to I only need to render the header once etc.. so in I have outlet to render the nested routes. How can I now use the with the nested routes. This was my homeModule

return (<>
<Header selectedPage={0} />
<main className="w-main flex-1 shadow-md m-auto p-5 my-5 rounded-sm flex">
  <Outlet/>
</main>
</>);
Enter fullscreen mode Exit fullscreen mode

And this is how I would want to have it work:

<Route path="/" element={<PrivateOutlet />}>
  <Route exact path="/requests" element={<RequestsPage />} />
  ...
</Route>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aspyryan profile image
Aspyryan

Whoops, I already figured it out, this was the code for my privateoutlet:

export default function PrivateOutlet() {
    const { account } = useContext(AccountContext);

    return account ? (
        <HomeModule>
            <Outlet />
        </HomeModule>
    ) : (
        <Navigate to="/login" replace />
    );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
iamandrewluca profile image
Andrei Luca

Yes 😊 I was about to write that you need to change PrivateOutlet

Collapse
 
tonnov profile image
Antonio Nogueda • Edited

is it necesary write Outlet twice?

Collapse
 
kisankumavat85 profile image
Kisan Kumavat

It helped a lot, Thanks buddy!

Collapse
 
iamandrewluca profile image
Andrei Luca

πŸ€— Glad could help!

Collapse
 
rajamuhammadasher profile image
Raja Muhammad Asher

Thank you. Helped a lot.

Collapse
 
iamandrewluca profile image
Andrei Luca

You are welcome! πŸ€—

Collapse
 
nivethan profile image
Nivethan Ariyaratnam

thanks man

Collapse
 
obriankevin11 profile image
obriankevin11 • Edited

How do you protect a set of routes using PrivateRoute?
Repeating ...

<Route path="/private1" element={
            <PrivateRoute>
              <Private1 />
            </PrivateRoute>
          }
        />

<Route path="/private2" element={
            <PrivateRoute>
              <Private2 />
            </PrivateRoute>
          }
        />
Enter fullscreen mode Exit fullscreen mode

... is not very elegant.
Thank you!

Collapse
 
brandonmcconnell profile image
Brandon McConnell

This article was a lifesaver as I migrate from React Router v5 to v6. Thank you! πŸ†οΈ

Collapse
 
nichitaa profile image
Pasecinic Nichita

You could use auth-react-router package npmjs.com/package/auth-react-router

It provides a really simple API to define your routes and few more configurations (like redirect routes for authorized and unauthorized routes, fallback component for each of the routes)

Just define your routes path and pages component and it will work

Collapse
 
sanyogchangmai profile image
Sanyog Changmai

Thank you very much....its very helpful

Collapse
 
stevereid profile image
Steve Reid

I might of missed something, but is there anything wrong with doing something like this?

<Route
  path="/"
  element={user ? <Home /> : <Navigate to="/login" />}
/>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
iamandrewluca profile image
Andrei Luca

πŸ€” looking at this it should work, I don't know if react-router can detect the full tree of routes using this way πŸ€”

Collapse
 
cebkun profile image
James Russel Ceballos

I think you should navigate to "/dashboard" (or wherever you want him to go after logging in) instead of "/login".