Guys anyone would know how to help me how to use Child routes with Lazy loading with react-router.
I'm doing it the way below but when I try to access the sub route it doesn't work
-- the route is declared as a array
import { lazy } from 'react'
const routesLIst = [
{
path: '/myPage',
name: 'myPage',
Component: lazy(() => import('src/pages/myPage')),
children: [
{ path: '/test', name: 'test', Component: lazy(() => import('src/components/myPage/test')) },
{ path: '/test2', name: 'test2', Component: lazy(() => import('src/components/myPage/test2')) },
]
}
-in the page I'm trying to acces the data with props.children, but the sub-route doesn't work
{ props.children }
example: I'm trying to access myPage/test , and get the data inside the myPage with props.children
const myPage: FC<> = (props) => {
const { children } = props
return (
<div>
{ children }
</div>
)
}
import {Route, Switch} from 'react-router';
interface Routes {
routes?: any
}
export const Routes: FC<Routes> = (props) => {
const {routes} = props
return (
<>
<Switch>
{routes.map((route) => {
if (!route.children || route.children.length < 1) {
return (
<Route
key={route.path}
exact={route.exact}
path={route.path}
render={props => (<route.Component {...props} name={route.name}/>)}
/>
)
} else {
return (
<Route
key={route.path}
exact={route.exact}
path={route.path}
render={props => (
<route.Component {...props} name={route.name}>
{
route.children.map((subRoute) => (
<Route
key={subRoute.path}
exact={subRoute.exact}
path={subRoute.path}
render={props => (
<subRoute.Component {...props} name={subRoute.name}/>
)}
/>
))
}
</route.Component>
)
}
/>
)
}
}
)}
</Switch>
</>
);
};
Related
Routes:
const issueSubmissionModal = () => IssueModal({ isVisible: true })
const renderHeader = () => <AppHeader userData={userData}><AppNavbar/></AppHeader>
const renderHome = () => <AppTemplate userData={userData} />
const Routes = userData ? [
{
key: "home",
path: "/",
component: renderHome(),
includeProfile: null,
includeAppheader: renderHeader()
},
{
key: "issueSubmission",
path: "/issueSubmission",
component: issueSubmissionModal(),
includeProfile: renderHome(),
includeAppheader: renderHeader()
},
] : []
const renderRoutes = () => <Switch>
{Routes.map(route => (
<Route exact path={route.path} key={route.key}>
{route.includeProfile && route.includeProfile}
{route.component}
</Route>
))}
</Switch>
return renderRoutes();
App.tsx:
function App() {
const { data: userData, isLoading, isError, error } = useAppUser();
return (
<div className="App">
<Router>
{userData ?
<Routes userData={userData} />
: null}
</Router>
</div>
);
}
export default App;
Problem? No matter what I do, all routes render as soon as the page launches. I simplified to two routes here, but the actual app has several.
It seems to me Switch isn't working. I don't know why.
I can go back to just having all the routes written out manually in App.tsx, but that's gross and inefficient for what we're trying to do.
Any React gurus got an idea?
Probably you are missing to include component tag within <Route />
{
Routes.map(route => (
<Route exact path={route.path} key={route.key} component=
{route.includeProfile ? route.component} />
)
)
}
Here is the code I am working with:
App.js
import NotFound from "components/NotFound";
import Loading from "components/Loading";
import PrivateRoute from "components/PrivateRoute";
import { logoutUser, setCurrentUser } from "features/auth/auth.slice";
import jwt_decode from "jwt-decode";
import React, { lazy, Suspense, useEffect } from "react";
import { useDispatch } from "react-redux";
import { Route, Switch, Redirect, useHistory } from "react-router-dom";
import HomeNav from "components/HomeNav";
// auth
const Login = lazy(() => import("features/auth/Login"));
const Register = lazy(() => import("features/auth/Register"));
const ForgotPassword = lazy(() => import("features/auth/ForgotPassword"));
const ResetPassword = lazy(() => import("features/auth/ResetPassword"));
// recipe
const Home = lazy(() => import("features/recipes/Home"));
const MyLikes = lazy(() => import("features/recipes/MyLikes"));
const MyRecipes = lazy(() => import("features/recipes/MyRecipes"));
const CreateRecipe = lazy(() => import("features/recipes/CreateRecipe"));
const EditRecipe = lazy(() => import("features/recipes/EditRecipe"));
const RecipeDetail = lazy(() => import("features/recipes/RecipeDetail"));
const UserProfile = lazy(() => import("features/recipes/UserProfile"));
const TopRecipes = lazy(() => import("features/recipes/TopRecipes"));
// user
const MyProfile = lazy(() => import("features/user/MyProfile"));
const App = () => {
const history = useHistory();
const dispatch = useDispatch();
// Check if user is already logged in
useEffect(() => {
const token = localStorage.token;
// Check for token to keep user logged in
if (token) {
try {
// Decode token and get user info and exp
const { exp, ...rest } = jwt_decode(token);
// Set user and isAuthenticated
dispatch(setCurrentUser(rest));
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (exp < currentTime) {
// Logout user
dispatch(logoutUser());
// Redirect to login
history.push("/");
}
} catch {
// Logout user
dispatch(logoutUser());
// Redirect to login
history.push("/");
}
}
}, []);
const authRoutes = [
{
path: "/",
Component: Login,
},
{
path: "/register",
Component: Register,
},
{
path: "/forgotpassword",
Component: ForgotPassword,
},
{
path: "/resetpassword/:resetToken",
Component: ResetPassword,
},
];
const privateRoutes = [
{
path: "/home",
Component: Home,
},
{
path: "/top-3",
Component: TopRecipes,
},
{
path: "/my-likes",
Component: MyLikes,
},
{
path: "/my-recipes",
Component: MyRecipes,
},
{
path: "/profile",
Component: MyProfile,
},
{
path: "/create",
Component: CreateRecipe,
},
{
path: "/recipe/:recipeId",
Component: RecipeDetail,
},
{
path: "/update/:recipeId",
Component: EditRecipe,
},
{
path: "/profile/:username",
Component: UserProfile,
},
];
return (
<Suspense fallback={<Loading />}>
<Switch>
{/* auth routes */}
{authRoutes.map(({ path, Component }, i) => (
<Route exact path={path} component={Component} key={i} />
))}
{/* private routes */}
{privateRoutes.map(({ path, Component }, i) => (
<PrivateRoute exact path={path} Component={Component} key={i} />
))}
{/* not found */}
<Route path="/404" component={NotFound} />
<Redirect to="/404" />
</Switch>
</Suspense>
);
};
export default App;
PrivateRoute.js
import React from "react";
import { Route, Redirect, useLocation } from "react-router-dom";
import { useSelector } from "react-redux";
import HomeNav from "components/HomeNav";
const PrivateRoute = ({ Component, ...rest }) => {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const { pathname } = useLocation();
return (
<Route
{...rest}
render={(props) =>
isAuthenticated ? (
<HomeNav>
<Component {...props} />
</HomeNav>
) : (
<Redirect to={{ pathname: "/", from: pathname }} />
)
}
/>
);
};
export default PrivateRoute;
This is the result I was looking for in that it renders the HomeNav component as a parent on all protected routes, but it causes a rerender each time a new link is visited. In addition, if I just wrap the PrivateRoutes inside of App.js in a HomeNav, it also works, but does not properly show the Not Found page. Any help would be appreciated!
You can render a single private Route component that specifies an array of private paths and render the HomeNav as a wrapper here and then render another Switch with the actual private paths.
App
<Switch>
{/* auth routes */}
{authRoutes.map(({ path, Component }, i) => (
<Route exact path={path} component={Component} key={i} />
))}
{/* private routes */}
<PrivateRoute path={privateRoutes.map(({ path }) => path)}>
<HomeNav>
<Switch>
{privateRoutes.map(({ path, Component }, i) => (
<PrivateRoute exact path={path} Component={Component} key={i} />
))}
</Switch>
</HomeNav>
</PrivateRoute>
{/* not found */}
<Route path="/404" component={NotFound} />
<Redirect to="/404" />
</Switch>
And of course you will need to remove HomeNav from PrivateRoute
const PrivateRoute = ({ Component, ...rest }) => {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const { pathname } = useLocation();
return (
<Route
{...rest}
render={(props) =>
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", from: pathname }} />
)
}
/>
);
};
I have a page which has three routes. The 3rd route has a tab component which handles 3 sub routes. I am able to navigate to Route 3, but unable to view the tabs and unable to render the content under each tab.
Please advice.
This is my code:
import "./styles.scss";
import React, { useState } from "react";
import { Redirect, Route, Switch } from "react-router";
import { BrowserRouter, Link } from "react-router-dom";
import { Tab, Tabs } from "#blueprintjs/core";
const ComponentC1 = () => <p>Component C1</p>;
const ComponentC2 = () => <p>Component C2</p>;
const ComponentC3 = () => <p>Component C3</p>;
const componentCRoutes = [
{
label: "Component C - 1",
code: "subC1",
component: ComponentC1
},
{
label: "Component C - 2",
code: "subC2",
component: ComponentC2
},
{
label: "Component C - 3",
code: "subC3",
component: ComponentC3
}
];
const ComponentA = () => <p>Component A</p>;
const ComponentB = () => <p>Component B</p>;
const ComponentC = (props) => {
const [tabId, setTabId] = useState(componentCRoutes[0].label);
const handleTabChange = (tabId) => setTabId(tabId);
return (
<>
<p>Component C</p>
<Tabs onChange={handleTabChange} selectedTabId={tabId}>
{componentCRoutes.map((tab) => {
return (
<Tab
key={tab.code}
id={tab.label}
title={
<Link to={`/${props.match.url}/${tab.code}`}>{tab.label}</Link>
}
/>
);
})}
</Tabs>
{(() => {
const { component, code } = componentCRoutes.find(
(item) => item.label === tabId
);
return (
<Route path={`${props.match.url}/${code}`} component={component} />
);
})()}
<Route exact path={props.match.url}>
<Redirect to={`${props.match.url}/${componentCRoutes[0].code}`} />
</Route>
</>
);
};
const routes = [
{ label: "Component A", path: "/routeA", component: ComponentA },
{ label: "Component B", path: "/routeB", component: ComponentB },
{ label: "Component C", path: "/routeC", component: ComponentC }
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<BrowserRouter>
{routes.map((item) => (
<Link key={item.path} to={item.path} style={{ paddingRight: "10px" }}>
{item.label}
</Link>
))}
<Switch>
{routes.map((route) => {
return (
<Route
key={route.path}
exact
path={route.path}
component={route.component}
/>
);
})}
<Route exact path="/">
<Redirect to="/routeA" />
</Route>
</Switch>
</BrowserRouter>
</div>
);
}
This is my codesandbox link
Please advice.
Issues:
Tabs in ComponentC are not working correctly as React-Router Link. It can be fixed using history.push in Tab's onChange handler.
You have not defined Routes in your nested component properly. You are using find to define the Route, that looks dirty. It can be fixed using a Switch and Route in nested component i.e. ComponentC
You used Route and Redirect to make default paths. That can be simplified as well.
You used props.match.url and props.match.path incorrectly. props.match.url (URL) should be used in Link or history.push and props.match.path (PATH) should be used in path of your nested Routes declarations.
Solution:
After fixing all the issues mentioned above, Here is the working code:
(Also, note that the Route that has nested routes should not be marked exact={true})
Main Routes:
const routes = [
{ exact: true, label: "Component A", path: "/routeA", component: ComponentA },
{ exact: true, label: "Component B", path: "/routeB", component: ComponentB }
{ exact: false, label: "Component C", path: "/routeC", component: ComponentC }
// ^ it is false because it has nested routes
];
// JSX
<BrowserRouter>
{routes.map((item) => (
<Link key={item.path} to={item.path}>
{item.label}
</Link>
))}
<Switch>
{routes.map((route) => {
return (
<Route
key={route.path}
exact={route.exact}
path={route.path}
component={route.component}
/>
);
})}
<Redirect exact from="/" to="/routeA" />
</Switch>
</BrowserRouter>
And Here is nested routes declarations inside ComponentC:
const routes = [
{
label: "Component C1",
code: "subC1",
component: ComponentC1
},
{
label: "Component C2",
code: "subC2",
component: ComponentC2
},
{
label: "Component C3",
code: "subC3",
component: ComponentC3
}
];
export default function ComponentC(props) {
const [tabId, setTabId] = useState(routes[0].code);
const handleTabChange = (tabId) => {
props.history.push(`${props.match.url}/${tabId}`);
setTabId(tabId);
};
return (
<>
<Tabs onChange={handleTabChange} selectedTabId={tabId}>
{routes.map((tab) => {
return <Tab key={tab.code} id={tab.code} title={tab.label} />;
})}
</Tabs>
<Switch>
{routes.map((route) => (
<Route
key={route.code}
exact
path={`${props.match.path}/${route.code}`}
component={route.component}
/>
))}
<Redirect
exact
from={props.match.url}
to={`${props.match.url}/${routes[0].code}`}
/>
</Switch>
</>
);
}
Here is full demo on Sandbox.
How to don't update component rendered by Route if something in redux changed? Added some examples of routing, component which lazy imported has redux store connection.
const ROUTES_OPTIONS = [
{
path: `${PATH_TO_SMART_ACCESS}${PATH_TO_SMART_ACCESS_VIEW}/options`,
Component: lazy(() => import("./SmartAccessActionsOptions")),
exact: false
},
{
path: `${PATH_TO_SMART_ACCESS}${PATH_TO_SMART_ACCESS_VIEW}/:id/options/:type?`,
Component: lazy(() => import("./SmartAccessActionsOptions")),
exact: false
}
];
const Routes = () => {
return (
<Switch>
{ROUTES_OPTIONS.map(route => (
<Route
key={route.path}
exact={route.exact}
render={() => {
const Component = route.Component;
return <Component />;
}}
/>
))}
</Switch>
);
};
I have a react router app:
export default () => (
<Router basename={process.env.REACT_APP_BASENAME || ""}>
<div>
{routes.map((route, index) => {
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return (
<route.layout {...props}>
<route.component {...props} />
</route.layout>
);
}}
/>
);
})}
</div>
</Router>
);
and this will render dfferent views based on the route clicked. the routes will render based on this object in a routes.js file:
export default [
{
path: "/login",
layout: DefaultLayout,
component: LogIn
}, .....]
To build in some authentication, I defined a PrivateRoute as:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
however, when i set the app as using PrivateRoute instead of normal Route (in the first snippet), the redirect does not use the routes object. How do I change the PrivateRoute const for a log in page reflect my original React Route architecture? what is the best practice?
Your code looks fine, but since you said your routes object is not understood by react-router maybe there is the case that your components aren't defined properly. For example, your components may be defined after the object is created. In that case, when that object is created, it will refer to undefined components. I made this mistake once, so I am just sharing what possibly went wrong.
Here is an example:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
function Public() {
return <h3>Public</h3>;
}
function Protected() {
return <h3>You can see protected content</h3>;
}
class Login extends Component {
state = { redirectToReferrer: false };
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
let { from } = this.props.location.state || { from: { pathname: "/" } };
let { redirectToReferrer } = this.state;
if (redirectToReferrer) return <Redirect to={from} />;
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
const routes = [
{
path: "/public",
component: Public,
private: false
},
{
path: "/login",
component: Login,
private: false
},
{
path: "/protected",
component: Protected,
private: true
}
];
function AuthExample() {
return (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
{routes.map((route, index) => {
if (route.private)
return (
<PrivateRoute
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
return (
<Route
key={index}
path={route.path}
exact={route.exact}
component={props => {
return <route.component {...props} />;
}}
/>
);
})}
</div>
</Router>
);
}
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
function PrivateRoute(props) {
const { component: Component, ...rest } = props;
return (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<AuthExample />, rootElement);
Notice Public, Protected and Login components are defined above the routes object. Defining them after the routes will result in errors.
I suggest to change your private route as following
const PrivateRoute = ({ component: Component, ...rest }) => fakeAuth.isAuthenticated === true ? (
<Route {...rest} component={component}
)} />
) : <Redirect to='/login' />;