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

Latest comments (61)

Collapse
 
dimasdevspro profile image
Dimas A Pereira

Thanks!

Collapse
 
chikephils profile image
Oreva Chike Philips

Hello, Just incase you see this, this is my protected route,

import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import {
selectIsAuthenticated,
selectUserLoading,
} from "../../features/user/userSlice";
import Loader from "../../components/Layout/Loader";

const ProtectedRoute = ({ Component }) => {
const isAuthenticated = useSelector(selectIsAuthenticated);
const loading = useSelector(selectUserLoading);

if (loading === true) {
return (




);
}
return isAuthenticated ? : ;
};

export default ProtectedRoute;

it Works fine but I want to improve it for user experience, when a user hard reloads the page from a browser, I want the user to be returned back to the page it was on, but it gets directed back to the Home page of the application if the user is logged it, but if the user is not logged it, it will go to the login page,
I also feel the glitch might be from my App.js component, which I will paste part of the code below,

function App() {
const dispatch = useDispatch();
const user = useSelector(selectUser);
const seller = useSelector(selectSeller);
const loading = useSelector(selectUserLoading);
const sellerLoading = useSelector(selectSellerLoading);
const allProducts = useSelector(selectAllProducts);
const allEvents = useSelector(selectAllEvents);
const token = localStorage.getItem("token");
const sellerToken = localStorage.getItem("seller_token");

useEffect(() => {
dispatch(getAllProducts());
dispatch(getAllEvents());

if (token) {
  dispatch(LoadUser());
}
if (sellerToken) {
  dispatch(LoadSeller());
}
Enter fullscreen mode Exit fullscreen mode

}, [dispatch, sellerToken, token]);
return (
<>

  <ThemeProvider theme={theme}>
    <div className="w-screen min-h-[100vh] lg:pr-2">
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route
          path="/login"
          element={
            <LoginRoute>
              <LoginPage />
            </LoginRoute>
          }
        />
        <Route
          path="/register"
          element={
            <LoginRoute>
              <RegisterPage />
            </LoginRoute>
          }
        />
        <Route
          path="/payment"
          element={
            <ProtectedRoute>
              <PaymentPage />
            </ProtectedRoute>
          }
        />
        <Route
          path="/activation/:activation_token"
          element={<ActivationPage />}
        />
        <Route
          path="/seller/activation/:activation_token"
          element={<SellerActivationPage />}
        />

        <Route path="/products" element={<ProductsPage />} />
        <Route path="/product/:id" element={<ProductDetailsPage />} />
        <Route path="/best-selling" element={<BestSellingPage />} />
        <Route path="/events" element={<EventPage />} />
        <Route path="/faq" element={<FAQPage />} />
        <Route
          path="/profile"
          element={<ProtectedRoute Component={ProfilePage} />}
        />
        <Route
          path="/profile/orders"
          element={<ProtectedRoute Component={OrderPage} />}
        />
        <Route
          path="/profile/refunds"
          element={<ProtectedRoute Component={RefundPage} />}
        />
        <Route
          path="/profile/inbox"
          element={<ProtectedRoute Component={InboxPage} />}
        />
        <Route
          path="profile/payment-method"
          element={<ProtectedRoute Component={PaymentMethodPage} />}
        />
        <Route
          path="profile/change-password"
          element={<ProtectedRoute Component={ChangePasswordPage} />}
        />
        <Route
          path="profile/address"
          element={<ProtectedRoute Component={AddressPage} />}
        />
Enter fullscreen mode Exit fullscreen mode

Spot how all warpped the protected Route inside my Route,

Kindly reply

Collapse
 
alexandrubarbulescu profile image
Alexandru Barbulescu • Edited

Could you please wrap children with fragment in "PrivateRoute" (<>children</>)?

Thank you!

Collapse
 
iamandrewluca profile image
Andrei Luca

Hi Alexandru! Why would that be needed πŸ€”

You use it like this

<PrivateRoute>
  <Private1 />
  <Private2 />
</PrivateRoute>
Enter fullscreen mode Exit fullscreen mode

Both private components are passed inside as children as an array of JSX Elements [JSX.Element, JSX.Element]

React knows how to render them.

Collapse
 
alexandrubarbulescu profile image
Alexandru Barbulescu

Hi Andrew!

In TypeScript (although this doesn't apply to your case, it can be confusing for beginners), the PrivateRoute component should return a JSX.Element. If the children prop is not wrapped in a React.Fragment (<>...</>), TypeScript will start to show an error:

PrivateRoute cannot be used as a JSX component.
Its return type 'Element | undefined' is not a valid JSX element.
Type 'undefined' is not assignable to type 'Element | null'.

Since a React component should return a JSX element or null, a correct implementation to solve this problem would be to wrap the children in a React.Fragment like this: <> {children} </>.

Hope this helps...

Thread Thread
 
iamandrewluca profile image
Andrei Luca

I see. Although this seems to happen only if the component is typed πŸ€”
Having a simple function as a component in TS I see no errors. Nevertheless, thanks for the tip, I'm updating the code.

Thread Thread
 
alexandrubarbulescu profile image
Alexandru Barbulescu

Yes. For me it was a bit confusing given that my components are typed. Thank you!

Collapse
 
lukrisum profile image
Yeee

Awesome !

Collapse
 
surajrp profile image
SURAJ_P

I am getting the duplicate records but my keys are different
Is it correct which I have implemented

PrivateRouter.js

import { Outlet, Navigate } from 'react-router-dom';
const PrivateRouter = (props) => {
const firstLogin = localStorage.getItem('firstLogin')
return firstLogin ? :
};

export default PrivateRouter;

App.js

      <Routes>
        <Route exact path="/" element={auth.token ? <Home/> : < Login/>} />
        <Route exact path="/register" element={<Register/>}/>
        <Route element={<Layout />}>
          <Route element={<PrivateRouter />}>
            <Route exact path="/:page" element={<PageRender/>}/>
            <Route exact path="/:page/:id" element={<PageRender/>} />
          </Route>
        </Route>
      </Routes>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
iamandrewluca profile image
Andrei Luca

Can you please review again your post, add more context to the subject, and fix the broken code? Ping me after that, and I'll take a look!

Collapse
 
surajrp profile image
SURAJ_P

How to call this is app.js
import { Outlet, Navigate } from 'react-router-dom';

const PrivateRouter = (props) => {
const firstLogin = localStorage.getItem('firstLogin')
return firstLogin ? :
};

export default PrivateRouter;

Collapse
 
mahedi5061 profile image
Mahedi Hassan Sharif

Awesome very Good Brother! Many Many Thanks.

Collapse
 
pabloalmonte profile image
Pablo Junior Almonte Avila

Amazing tutorial, thank you ❀️

Collapse
 
dongloo profile image
Minh Kì Đông

Great

Collapse
 
ronakptl996 profile image
Patel Ronak

Nice πŸš€πŸš€πŸš€

Collapse
 
thatgriffith profile image
Kevin H Griffith

Awesome man! Huge thanks πŸ™

Collapse
 
masudur10 profile image
Masudur Rahman

In this react-router version 6 implementation,how i can show alert or popup message for unlogged user when i trying to navigate private route? if user not logged-in it will show the message "please login first" with login button to navigate login page.

if user not logged-in, user always navigate/redirected to home page or other public route.

Collapse
 
iamandrewluca profile image
Andrei Luca

See this example, when you do the redirect, you can pass some state
in the login component use that state to show the message

dev.to/iamandrewluca/comment/1jjm9

Collapse
 
masudur10 profile image
Masudur Rahman

In this react-router version 6 implementation,how i can show alert or popup message for unlogged user when i trying to navigate private route? if user not logged-in it will show the message "please login first" with login button.

"< L i n k to='/login' > login< L i n k >"

if user not logged-in, user always navigate/redirected to home page or other public route.

Collapse
 
masudur10 profile image
Masudur Rahman • Edited

In this react-router version 6 implementation,how i can show alert or popup message for unlogged user when i trying to navigate private route? if user not logged-in it will show the message "please login first" with login button.

"< L i n k to='/login' > login< L i n k >"

if user not logged-in, user always navigate/redirected to home page or other public route.

Collapse
 
ficazzo profile image
Femi Obadimu

thanks