So this question is more about the approach I should take to solve this problem.
I have a JSON like this
const app = [
{
tab: 'Home',
// content: '<div>This is home</div>',
nav: [
{
tab: 'Dashboard',
content: '<div>This is Dashboard</div>'
},
{
tab: 'Profile',
content: '<div>This is Profile</div>'
},
]
},
{
tab: 'About',
content: '<div>This is About</div>'
},
{
tab: 'Pricing',
content: '<div>This is Pricing</div>'
},
];
Now what I would want is setup the entire routes and pages to render the above JSON.
Below is the pseudo code:
Approach 1...
Loop through and for each nav/subnav add Route.
import {
BrowserRouter,
Route,
NavLink,
} from 'react-router-dom';
const Page = (content) => {
return (
<div>{content}</div>
)
}
<BrowserRouter>
app.map((nav, i) =>
<NavLink key={nav.tab} to={`/${nav.tab}`} activeClassName="active">
{nav.tab}
</NavLink>
<Route
path={`/${nav.tab}`} render={(props) => (
<Page {...props} pageData={nav.content}>
if(nav.subNav) {
nav.subNav.map((subnav, i) =>
<NavLink key={subnav.tab} to={`/${nav.tab}/${subnav.tab}`} activeClassName="active">
{nav.tab}
</NavLink>
<Route
path={`/${nav.tab}/${subnav.tab}`} render={(props) => (
<Page {...props} pageData={subnav.content} />
)}
/>
)
}
</Page>
)}
/>
)
</BrowserRouter>
Approach 2
Would something like this work/be better? Just have 2 routes returning same component, pass the app object to Page and then based on URL render the corresponding data
const Page = (app) => {
return (
// Loop over app
if(mainNav from URL == tab) {
if(subNav from URL == tab) {
<div>{content}</div>
}
}
)
}
<BrowserRouter>
<Route
path={'/:mainNav'} render={(props) => (
<Page {...props} pageData={app} />
)}
/>
<Route
path={'/:mainNav/:subNav'} render={(props) => (
<Page {...props} pageData={app} />
)}
/>
</BrowserRouter>
Thanks for reading through and appreciate your help. If theres another better approach I would love to know!
Try utilizing the exact prop offered in your <Route /> components.
<Route exact path="/foo" component={Foo} />
If you have the routes:
/foo
/foo/bar
/foo/bar/baz
/foo matches in all three cases.
Related
I'm newbie with react but i saw something weird. I've got all of my routes something like below:
<Main>
<Route exact path="/home" component={Home} />
<Route exact path="/home1" component={Home1} />
<Route exact path="/home2" component={Home2} />
<Route exact path="/home3" component={Home3} />
<Route exact path="/home4" component={Home4} />
<Route exact path="/home5" component={Home5} />
<Redirect from="*" to="/home" />
</Main>
is there any way to use an array and map instaed of paste another one <Route /> component?
I think about somehting like this:
routes.ts
const routes = [
{ path: "/home", component: SomeComponent },
{ path: "/home2", component: SomeComponent2 },
];
indes.tsx
<Main>
{ routes.map(route => (
<Route exact path={route.path} component={route.component} />
))}
</Main>
but i don't know how to pass new component into object in routes list.
Thanks for any help!
Note: Which react-router-dom version are u using? This is a code example for RRD6
const routes = [
{ name: "/route1", component: <ClickLabel /> },
{ name: "/route2", component: <Focus /> },
{ name: "/route3", component: <Counter /> }
];
/*--------------***/
{routes.map(({ name, component }, i) => (
<Route key={i} path={name} element={component} />
))}
Here is example:
https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/App.js
If u are using RRDv5, then here is the code
{/* Want to pass some dynamic props */}
{routes.map(({ name, component }, i) => (
<Route
key={i}
path={name}
render={() => {
return React.createElement(
component,
{ p: "THis is the prop" },
null
);
}}
/>
))}
{/* Simply render a component */}
{routes.map(({ name, component }, i) => (
<Route key={i} path={name} component={component} />
))}
I've got a navigation that I am having a hard time trying to style. I have been able to add active classes to the navigational elements and apply styles to them, however the main dashboard which functions as a homepage does not get the dedicated active class applied to it when I click it. After doing a little bit of digging around in the app, I believe the AppRouter.js or routeConfig.js might be the issue.
The navigational elements are displayed with the Navlink code below:
renderNavigationItem = (navigationItem) => {
const { classes } = this.props;
const { id, name, parentUrlLink } = navigationItem;
return (
<NavLink exact key={id.toString()} to={{ pathname: parentUrlLink }} className="desk-link"
activeClassName='is-active'>
<MenuItem className={classes.fontBoldHeader}>{name}</MenuItem>
</NavLink>
);
}
The app router looks like this:
class AppRouter extends Component {
constructor(props, context) {
super(props);
this.context = context;
}
render() {
const { account } = this.context;
return (
<Switch>
{account ? <Redirect exact from="/" to={{ pathname: '/dashboard' }} /> : <Route exact path="/" component={Home} />}
{routes.map((item) => <PrivateRoute {...item} key={`${item.path}-route`} />)}
<Route path="/sessiontimeout" render={(props) => <TimedOutPage {...props} />} />
<Route path="/500" render={(props) => <Error errorCode="500" {...props} />} />
<Route path="/503" render={(props) => <Error errorCode="503" {...props} />} />
<Route path="*" render={(props) => <Error errorCode="404" {...props} />} />
</Switch>
);
}
}
It looks like there's a specific rule that is in place for the "dashboard" page. It is also the page people go to if they click the logo so I feel like there is a specific rule that is preventing the dashboard page to inherit the "is-active" class. I am new to React so this is something that is a little over my head at the moment and any help would be greatly appreciated!
Edit: Here is where rednerNavigation is called:
renderNavigation = () => {
const { menuNavigation } = this.props;
return menuNavigation.map((item) => {
if (item.children.length > 0) {
return this.renderNavigationItemWithSubNav(item);
}
return this.renderNavigationItem(item);
});
}
I'm trying to find a way to organize my routes to assist the dev who might be taking over my work in the future. I thought of separating my <Route /> entries into separate components and then just load those into a main component similar to how users are assigned groups.
The issue is that when using more than one component only the first one works. This might not be the most react way of doing this so I'm also open to alternatives.
Original route arrangement
const AllRoutes = () => {
return (
<Switch>
{/* public routes*/}
<Route path={'/about'} component={AboutView} />
<Route path={'/project'} component={ProjectView} />
<Route path={'/contact'} component={ContactView} />
{/* auth routes */}
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
<Route component={Error404View} />
</Switch>
)
}
Separating the public routes from the auth ones:
const PublicRouteGroup = () => {
return (
<>
<Route path={'/about'} component={AboutView} />
<Route path={'/project'} component={ProjectView} />
<Route path={'/contact'} component={ContactView} />
</>
)
}
const AuthRouteGroup = () => {
return (
<>
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
</>
)
}
This way I can use it as such:
const AllRoutes = () => {
return (
<Switch>
<PublicRouteGroup /> {/* This works */}
<AuthRouteGroup /> {/* This doesn't */}
{/* This 404 is not a route group */}
<Route component={Error404View} />
</Switch>
)
}
Flipping <PublicRouteGroup /> and <AuthRouteGroup /> only changes the order:
const AllRoutes = () => {
return (
<Switch>
<AuthRouteGroup /> {/* This works */}
<PublicRouteGroup /> {/* This doesn't */}
{/* This 404 is not a route group */}
<Route component={Error404View} />
</Switch>
)
}
Update #1
This is thanks to #skyboyer. By moving the <Switch> to the child components and removing it from the AllRoutes component each component started to show. It appears adding the <Switch> in AllRoutes is allowing only the first hit to show which is as <Switch> does. But now by removing it it shows the 404 at the end of each page as well.
Basically, it looks like this:
const AllRoutes = () => {
return (
<>
<Route component={AuthRouteGroup} /> {/* This works */}
<Route component={PublicRouteGroup} /> {/* This also works */}
{/* This 404 is not a route group */}
<Route component={Error404View} /> {/* Always shown at the bottom */}
{/* Even putting the 404 in its own RouteGroup yields the same issue */}
</>
)
}
It appears this current set up of treating components like OOP classes you can extend from is the wrong approach. I've instead made use of arrays since these can be acted upon by the spread operator. It still accomplishes the same goal of organizing routes across an infinite number of groups which was what I was after.
Create the array for each group
const public_route_group = [
{path: '/about', component: AboutView},
{path: '/project', component: ProjectView},
{path: '/contact', component: ContactView},
]
const auth_route_group = [
{path: '/login', component: LoginView},
{path: '/logout', component: LogoutView},
]
const error_route_group = [
{component: Error404View} // No path required
]
const user_routes = [
...public_route_group,
...auth_route_group,
...error_route_group
]
Create the routes
const AllRoutes = () => {
return (
<Switch>
{user_routes.map((route, idx) => {
return <Route key={idx} {...route} />
})}
</Switch>
)
}
I figure this can also be modified further if you're using nested objects in your array.
I'd like to thank #skyboyer for providing an insight into this problem.
How about having it without Swtich at top-level
<Route component={PublicRouteGroup} />
<Route component={AuthRouteGroup} />
so they are rendered unconditionally. And then having extra Switch in your components like
const AuthRouteGroup = () => {
return (
<Switch>
<Route path={'/login'} component={LoginView} />
<Route path={'/logout'} component={LogoutView} />
<Switch/>
)
}
But why id did not work?
The reason is how Switch works:
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
See, even if AuthRouteGroup is not a Route, Switch anyway looks to its props.path. And once undefined for props.path matches any path and Switch renders only first matching Route you are getting only first component rendered.
[UPD] "does-not-match-any-route" View will work only at top level of Switch. Also there are no way to know if some nested children of sibling element has matched current route or not. So only way I see is listing all routes in single place.
Alternative that looks rather poor is having special route "/error404" and redirect user to it from inside of other components(but who should decide? and where? and when?).
I'm using react-router-dom and generate my routes dynamically from an array like so
Routes Data
const routes = [
{
path: '/',
exact: true,
component: () => <MyComponent />
}
}
Route Generation
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
})}
Render Prop
I found out about the render() prop I could define but even so how do I do this since the component is inside a variable
const props = this.props;
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <??? {...props} />
/>
})}
How can I pass this.props during the route generation?
You could change your array to just include the component instead of a new function component that renders the component and you will get the props sent to it:
const routes = [
{
path: '/',
exact: true,
component: MyComponent
}
}
You can use any capitalized variable as a component, so you could also do this if you prefer:
{routes.map((route, index) => {
const Component = route.component;
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <Component {...props} />
/>
})}
I am using react-router-dom 4.2. I have my App.js with Authenticated components inside. This components are created by me and add a little of business logic, create the component via React.createElement, and route them via Route component. Nothing unusual.
The App.js:
// ...
const App = props => (
<BrowserRouter>
<Fragment>
<Switch location={location}>
<Route
exact
path={URLS.ROOT}
render={() => <Redirect to={URLS.DASHBOARD} />}
/>
<Authenticated
{...props}
resource={ResourcesCode.DASHBOARD}
patent={PatentsCode.VIEW}
path={URLS.DASHBOARD}
component={Dashboard}
/>
<Authenticated
{...props}
resource={ResourcesCode.SUBSCRIBE}
patent={PatentsCode.VIEW}
path={URLS.SUBSCRIBE}
component={Subscribe}
/>
</Fragment>
</BrowserRouter>
// ...
Inside of the component Subscribe (mentioned above in the 2nd Authenticated component), I have more routes as you can see below:
// ...
<Route
path={URLS.SUBSCRIBE}
exact
render={() => (
//...
)}
/>
<Route
path={URLS.SUBSCRIBETWO}
exact
render={() => (
//...
)}
/>
// ...
The point is that this routes on the child component (Subscribe) are ignored.
Why are them ignored? How can I solve it?
I really need this routes inside the child component. I don't want to move them to App.js
IMPORTANT EDIT:
The second route is ignored, I realized that the first doesn't. In other words, The Route component with path={URLS.SUBSCRIBE} is working, but the component with path={URLS.SUBSCRIBETWO} is ignored, so here is the problem to solve.
EDIT:
For if you need, the Authenticated component:
// ...
}) => (
<Route
path={path}
exact={exact}
render={route => {
if (!authenticated) {
if (loggingIn) {
return '';
}
return <Redirect to={URLS.LOGIN} />;
}
if (!roleSubReady) {
return '';
}
if (path !== URLS.SUBSCRIBE && user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.SUBSCRIBE} />;
}
if (path === URLS.SUBSCRIBE && !user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.DASHBOARD} />;
}
if (resource && !checkPermission(user, resource, patent)) {
return <NotAuthorized history={route.history} />;
}
return (
<React.Fragment>
<Menu user={user} path={path} isLoading={isLoading} />
<Header show={showHeaderAndFooter} user={user} path={path} />
<MainContent>
{React.createElement(component, {
user,
resource,
...route,
})}
<Footer show={showHeaderAndFooter} />
</MainContent>
</React.Fragment>
);
}}/>
);