In ProtectedRoute.js I have coded:
const ProtectedRoute = ({ component: Component, ...rest }) => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
return (
<Fragment>
{!loading && (
<Routes>
<Route
{...rest}
render={(props) => {
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <Component {...props} />;
}}
/>
</Routes>
)}
</Fragment>
);
};
export default ProtectedRoute;
and in App.js I have written as:
function App() {
const { isAuthenticated, user } = useSelector((state) => state.user);
useEffect(() => {
WebFont.load({
google: { families: ["Roboto", "Droid Sans", "Chilanka"] },
});
store.dispatch(loadUser());
}, []);
return (
<Router>
<Header />
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/product/:id" element={<ProductDetails />} />
<Route exact path="/products" element={<Products />} />
<Route path="/products/:keyword" element={<Products />} />
<Route exact path="/search" element={<Search />} />
<Route exact path="/login" element={<Authenticate />} />
<ProtectedRoute exact path="/account" element={<Profile />} />
</Routes>
<Footer />
</Router>
);
}
export default App;
Error says: [ProtectedRoute] is not a Route component. All component children of Routes must be a Route or <React.Fragment>.
Is there something missing! Thank you
CodePudding user response:
In react-router-dom
custom route components are no longer used. Routes
components can have only Route
and React.Fragment
components as children, and Route
components can have only Routes
or other Route
components as a parent.
Instead wrapper components handle the business logic and either render the children
prop or an Outlet
for nested Route
components, or the Navigate
for redirection.
Render children
const ProtectedRoute = ({ children }) => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
if (loading) return null;
return isAuthenticated
? children
: <Navigate to="/login" replace />;
};
...
<Route
path="/account"
element={(
<ProtectedRoute>
<Profile />
</ProtectedRoute>
)}
/>
Render Outlet
import { Outlet } from 'react-router-dom';
const ProtectedRoute = () => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
if (loading) return null;
return isAuthenticated
? <Outlet />
: <Navigate to="/login" replace />;
};
...
<Route path="/account" element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} />
</Route>
The benefit of using the Outlet
is you can use a single auth wrapper component and render any number of nested Route
children into them, whereas with the children
method you cannot render nested routes unless you wrap them in a Routes
component.
CodePudding user response:
How about flipping the logic around and checking everything inside ProtectedRoute like this?
const ProtectedRoute = () => {
const { loading, isAuthenticated, user } = useSelector((state) => state.user);
if (loading) { return null; }
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <Profile user={user} andWhateverElse={true} />;
};
export default ProtectedRoute;
And
function App() {
const { isAuthenticated, user } = useSelector((state) => state.user);
useEffect(() => {
WebFont.load({
google: { families: ["Roboto", "Droid Sans", "Chilanka"] },
});
store.dispatch(loadUser());
}, []);
return (
<Router>
<Header />
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/product/:id" element={<ProductDetails />} />
<Route exact path="/products" element={<Products />} />
<Route path="/products/:keyword" element={<Products />} />
<Route exact path="/search" element={<Search />} />
<Route exact path="/login" element={<Authenticate />} />
<Route exact path="/account" element={<ProtectedRoute />} />
</Routes>
<Footer />
</Router>
);
}
export default App;