React Router is not rendering - reactjs

I am trying to understand nested react router structure. I have implemented some code from a tutorial but it is not working as expected. Here is my routes.js file.
import React from "react";
import {
BrowserRouter as Router,
Switch
} from "react-router-dom";
const ROUTES = [
{ path: "/", key: "ROOT", exact: true, component: () => <h1>Log in</h1> },
{
path: "/app",
key: "APP",
component: RenderRoutes,
routes: [
{
path: "/app",
key: "APP_ROOT",
exact: true,
component: () => <h1>App Index</h1>,
},
{
path: "/app/page",
key: "APP_PAGE",
exact: true,
component: () => <h1>App Page</h1>,
},
],
},
];
export default ROUTES;
function RouteWithSubRoutes(route) {
return (
<Router
path={route.path}
exact={route.exact}
render={props => <route.component {...props} routes={route.routes} />}
/>
);
}
export function RenderRoutes(routes ) {
return (
<Switch>
{routes.map((route, i) => {
return <RouteWithSubRoutes key={route.key} {...route} />;
})}
<Router component={() => <h1>Not Found!</h1>} />
</Switch>
);
}
I have imported
BrowserRouter
in index.js as usual. In my App component I tried to render RenderRoutes(ROUTES):
function App() {
return (
<div style={{ display: "flex", height: "100vh", alignItems: "stretch" }}>
<div style={{ flex: 0.3, backgroundColor: "#f2f2f2" }}>route menu</div>
<div>
{/* <RenderRoutes routes={ROUTES} /> This way also is not working*/}
{RenderRoutes(ROUTES)}
</div>
</div>
);
}
Here I am trying to render subcomponents but the page is not showing anything except (root menu) text from app.js. Let me know what am I doing wrong. Thanks in advance

The react router structure usually follows 1 Switch 1 Router and multiple Routes
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route path="/YOUR_PATH">
<BlogPost />
</Route>
</Switch>
</Router>,
You are returning multiple Router in RouteWithSubRoutes change that to Route
function RouteWithSubRoutes(route) {
return (
<Route
path={route.path}
exact={route.exact}
render={props => <route.component {...props} routes={route.routes} />}
/>
);
}

Related

React Router v6 and recursive rendering of Route component from data

Following up from my question React router v6 and relative links from page within route, I'm trying to refactor the routes in our app to be more nested.
Trouble is that it doesn't seem possible to render a Route element recursively from data, because react-router insists that Route is directly inside Route and not wrapped in another component, and I cannot see how to render recursively (to arbitrary depth) any other way.
Reproduction on codesandbox.
import React from "react";
import { BrowserRouter, Routes, Route} from "react-router-dom";
import "./styles.css";
function GenericPage() {
return <div className="page">Generic page</div>;
}
const nav = {
slug: "",
title: "Home",
children: [
{
slug: "foo",
title: "Foo"
},
{
slug: "bar",
title: "Bar"
}
]
};
const RecursiveRoute = ({ node }) => {
return (
<Route path={node.slug} element={<GenericPage />}>
{node.children?.map((child) => (
<RecursiveRoute node={child} />
))}
</Route>
);
};
export default function App() {
return (
<BrowserRouter>
<Routes>
<RecursiveRoute node={nav} />
</Routes>
</BrowserRouter>
);
}
Error from react-router:
[RecursiveRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Issue
As the error indicates, you can't render Route components directly, they must be rendered directly by a Routes component, or another Route component in the case of nested routes.
Solution
Refactor RecursiveRoute to render a Routes component with a route for the current node and then map the node's children to routes that render the RecursiveRoute as an element.
Example:
function GenericPage({ title }) {
return (
<div className="page">
{title} page
</div>
);
}
const RecursiveRoute = ({ node }) => (
<Routes>
<Route
path={`${node.slug}/*`}
element={<GenericPage title={node.title} />}
/>
{node.children?.map((child) => (
<Route
key={child.slug}
element={<RecursiveRoute key={child.slug} node={child} />}
/>
))}
</Routes>
);
Suggestion
I strongly suggest not trying to roll your own custom route configuration and renderer, use the useRoutes hook instead to do all the heavy lifting for you.
Example:
Refactor the navigation config:
const nav = [
{
path: "/",
element: <GenericPage title="Home" />,
children: [
{
path: "foo",
element: <GenericPage title="Foo" />
},
{
path: "bar",
element: <GenericPage title="Bar" />
}
]
}
];
Pass the config to the useRoutes hook and render the result:
const routes = useRoutes(nav);
...
return routes;
Demo
I had the same puzzle to solve. In general I solve it by passing a function in Routes component. Here is my solution with few code snippets.
// in Routes.ts
interface IRoutes {
path: string
component: JSX.Element
children?: IRoutes[]
}
const routes: IRoutes[] = [
{
path: 'warehouse'
component: <WarehousePage />
children: [
{
path: 'products'
component: <ProductsPage />
},
{
path: 'units'
component: <UnitsPage />
},
]
},
]
// in AppRouter.tsx
const renderRoutesRecursive = (routes: IRoutes[]) =>
routes.map((route, index) =>
route.children ? (
renderRoutesRecursive(route.children)
) : (
<Route
key={index}
path={route.path}
element={route.component}
/>
),
)
const renderRoutes = useMemo(() => renderRoutesRecursive(routes), [routes])
return (
<Routes>
<Route path='/' element={<Layout />}>
{renderRoutes}
</Route>
</Routes>
)
// in Layout.tsx
const Layout = () => {
return (
<>
<Header />
<Navigation />
<Main>
<Outlet />
</Main>
<Footer />
</>
)
}

PrivateRoutes in React dom V6. I'd Like to know how to convert this private routes to v6 format. I have tried but failed [duplicate]

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

React hooks router is not rendering component

Im trying to lazy load my components with react-router. But component is not rendering after redirect. I have to refresh page manually to see the component. Where is my fault ?
routes.js ;
import React from "react";
const Bank = React.lazy(() => import("containers/HomePage"));
const MoneyWithdraw = React.lazy(() => import("containers/Withdraw"));
const routes = [
{ path: "/main", exact: true, component: Bank },
{ path: "/withdraw", exact: true, component: MoneyWithdraw },
];
export default routes;
index.js ;
<div
hidden={process.env.NODE_ENV === 'development' ? !hidden : hidden}
className={hidden ? 'app-show' : 'app-hide'}>
<Suspense fallback={<div>test</div>}>
<Router history={history}>
<MenuSideBar />
<Switch>
{routes.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
render={props => <route.component {...props} />}
/>
) : null;
})}
</Switch>
</Router>
</Suspense>
</div>
I used history as ;
import { createHashHistory } from 'history';
export default createHashHistory();

React router - code splitting lazy imports. Switch with Suspense outside

I'm using module federation from webpack and my core app contained all routes to the rest of the app. What works fine is that inside the Switch, I just had each AuthRoute or Route manually rather than using the map. Suspense was wrapping the Switch so that the direct children are just Route. I'm now doing some splitting but I can't get it to work. Any ideas?
my routes are set up as so (and localRoutes is at the bottom):
const routes = [
...localRoutes,
// ...remoteRoutes
];
Inside my BrowserRouter I map routes based on whether the user is authorised for that route or not. I suspect the problem is here but don't understand why Route or AuthRoute that returns a Route won't work since it's a directly under the Switch.
<Switch>
{routes.map((route) => {
console.log(route)
route.auth ?
<AuthRoute
key={route.path}
path={route.path}
component={route.component}
exact={route.exact}
requiredRoles={route.requiredRoles}
/>
:
<Route
key={route.path}
path={route.path}
component={route.component}
exact={route.exact}
/>
})}
<Redirect to='/login' />
</Switch>
where authRoute:
const AuthRoute = ({ Component, path, exact, requiredRoles }) => {
const isLoggedIn = true // or false
const roles = ['admin', 'sth_else']
const userHasRequiredRole = intersection(requiredRoles, roles).length > 0
const message = userHasRequiredRole ? 'Please log in to view this page' : "You can't be here!"
return (
<Route
exact={exact}
path={path}
render={(props) =>
isLoggedIn && userHasRequiredRole
? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: userHasRequiredRole ?
'/login' :
'/modules',
state: {
message,
requestedPath: path
}
}}
/>
)
}
/>
);
};
export default AuthRoute;
and example routes:
const AboutPage = lazy(() => import('core/AboutPage'))
const LoginPage = lazy(() => import('core/LoginPage'))
const MyModules = lazy(() => import('core/MyModules'))
const routes = [
{
auth: true,
path: "/modules",
component: MyModules,
exact: false,
requiredRoles: [
String(UserRoles.Administrator),
String(UserRoles.AnotherRole),
String(UserRoles.Another)
]
},
{
auth: false,
path: "/about",
component: AboutPage,
exact: false,
}
];
If you are doing lazyLoad specify component as a function inside <Route>.
In your case try the following:
<Route
key={route.path}
path={route.path}
component={(props) => (<route.component {...props} />)}
exact={route.exact}
/>
I think the above code should work if you add a return before ternary operator
retur route.auth ?...
I am having similar structure in my app
<Suspense fallback={<Spinner />}>
<Switch>
<Refresh path="refresh" />
{routes.map((route) => {
return <PrivateRoute key={route.path} {...route} user={user} />;
})}
{routes.length && user && <Route component={NotFound} />}
</Switch>
</Suspense>

How to configure react-route with private route?

I cannot configure the route correctly. Shop component not renders
Component structure:
index.tsx:
const Index: FC = () => {
const SignIn = props => (
<LazyLoad
component={React.lazy(() => import('./modules/auth/containers/SignIn'))}
{...props}
/>
)
return (
<Provider store={store}>
<EmotionThemeProvider>
<ThemeProvider theme={theme}>
<Router>
<Switch>
<PrivateRoute exact path='/' component={Main} />
<Route path='/signin' component={SignIn} />
</Switch>
</Router>
</ThemeProvider>
</EmotionThemeProvider>
</Provider>
)
}
PrivateRoute.tsx:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
localStorage.getItem('accessToken') ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: '/signin', state: { from: props.location } }}
/>
)
}
/>
)
Main.tsx:
const Main: FC = () => (
<App>
<Switch>
{routes.map(route => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</App>
)
Shop.tsx:
const Shop = () => {
const [, setCollapsed] = useContext(SidebarContext)
useEffect(() => {
setCollapsed(true)
}, [])
return <div>Shop</div>
}
routes.ts:
export const routes = [
{
path: '/shop',
key: 'market',
component: props => (
<LazyLoad
component={React.lazy(() => import('./modules/shop/pages/Shop'))}
{...props}
/>
),
},
{
path: '/',
exact: true,
component: () => <div>main</div>,
skip: true,
},
]
When I logged in, the main page opens, but after when I go to the shop, it no longer renders.
Where is my mistake? I have tried to remove 'exact' from PrivateRoute, then shop opens, but I got this error:
Uncaught Error: Maximum update depth exceeded. This can happen when a
component repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops

Resources