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

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?

0

19 Answers

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.

9

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> }
/>
6

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

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.

0

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!

2

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" />}/>

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.

1

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 />} /> )
)}
1

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/>}/>

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>

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

Here's my solution that manages public, protected and fallback routes using useRoutes hook in a clean manner.

  1. Make a ProtectedRoute component.
import React from 'react';
import { useLocation, Navigate } from 'react-router-dom';
export function ProtectedRoute({ children }: React.PropsWithChildren) { const [isLoggedIn] = your-auth-system(); const { pathname } = useLocation(); if (!isLoggedIn && pathname) { return <Navigate to={`/login?from=${pathname}`} />; } return children;
}

  1. Define all the routes in their individual files.

public.ts

import App from '../App';
import { Login } from '../pages';
export const publicRoutes = [ { path: '/', element: <App />, }, { path: '/login', element: <Login />, }
];

private.ts

import { Catalog, Dashboard,
} from '../pages';
export const privateRoutes = [ { path: '/catalog', element: <Catalog />, }, { path: '/dashboard', element: <Dashboard />, }
];

fallback.ts

import { NotFound } from '../pages';
export const fallbackRoute = [ { path: '*', element: <NotFound />, },
];

  1. Parse all the routesAppRoutes.tsx
import React from 'react';
import { RouteObject, useRoutes } from 'react-router-dom';
import { publicRoutes } from './public';
import { privateRoutes } from './private';
import { fallbackRoute } from './fallback';
import { ProtectedRoute } from '../components';
type Route = { path: string; element: JSX.Element;
};
export function AppRoutes() { const parseRouteObjects = ( routes: Route[], isPrivate: boolean = false ): RouteObject[] => { return routes.map((route) => ({ path: route.path, element: isPrivate ? ( <ProtectedRoute>{route.element}</ProtectedRoute> ) : ( route.element ), })); }; const publicRouteObjects = parseRouteObjects(publicRoutes); const privateRouteObjects = parseRouteObjects(privateRoutes); const fallbackRouteObjects = parseRouteObjects(fallbackRoute); const routes = [ ...publicRouteObjects, ...privateRouteObjects, ...fallbackRouteObjects, ]; const allRoutes = useRoutes(routes); return <React.Fragment> {allRoutes} </React.Fragment>;
}

  1. Register the routes in the appindex.ts
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { BrowserRouter } from 'react-router-dom';
import { Layout } from './components';
import { AppRoutes } from './routes/AppRoutes';
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <BrowserRouter> <Layout> <AppRoutes /> </Layout> </BrowserRouter> </React.StrictMode>
);

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>
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 />;
}

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} />} />
<Route path='/' element={<Navigate to="/search" />} />

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

You Might Also Like