I'm using React Router v5 and I ran into problems when trying to define routes split across multiple components. The case I tried is something like the following:
<Switch>
<MyRoutes />
<Route path="/foo">
<Foo />
</Route>
</Switch>
with the MyRoutes component looking like
<>
<Route path="/bar">
<Bar />
</Route>
<Route path="/baz">
<Baz />
</Route>
</>
The problem now is that routes declared after the custom MyRoutes component don't work, the custom component seems to match the route without rendering anything. The routes inside it do actually work as expected.
The reason I'm splitting the routes like this, apart from organizing related routes together is that I also need the separate routing component as a standalone to be able to integrate a set of components into a legacy web application. And I'd like to avoid duplicating the routes here for that purpose.
But obviously I'm doing something that you're not supposed to do with React Router, though I'm not entirely sure why this is an issue. So I would like to understand the limitation here a bit more to avoid running into variations of this again in the future. Why can't I split Routes like this into separate components within a single Switch, what are the exact limitations here?
Is putting a separate Switch into each component the right answer here? This does seem wrong from a conceptual point of view as each Route is supposed to be exclusive, only one of them should ever be rendered. Multiple Switch components would allow multiple components to be rendered at the same time, even though in practice this should not happen as the routes should be exclusive and not overlap.
What is the proper, intended way to have modular Routes in React Router v5 and not a single huge Routing component?
Issue
Routers inclusively render all matching routes whereas the Switch component exclusively renders the first match it finds. It's not strictly enforced but the only valid children of the Switch component are the Route and Redirect components. If you render any other component inside the Switch it will be returned and rendered and route matching completes, any routes after won't be reachable.
Switch
Renders the first child <Route> or <Redirect> that matches the
location.
How is this different than just using a bunch of <Route>s?
<Switch> is unique in that it renders a route exclusively. In
contrast, every <Route> that matches the location renders
inclusively.
Solution
Render MyRoutes in a route on its own so the Switch component can manage path matching and rendering. I suggest using path="/" so the nested routes can also further be matched.
<Switch>
<Route path="/foo">
<Foo />
</Route>
<Route
path="/" // <-- allows matching "/bar" and "/baz"
component={MyRoutes}
/>
</Switch>
MyRoutes
You will also want to render these routes into a Switch component. Any time you want to exclusively match routes you'll use the Switch component.
<Switch>
<Route path="/bar">
<Bar />
</Route>
<Route path="/baz">
<Baz />
</Route>
</Switch>
Also keep in mind that within the Switch component that path order and specificity matters. You will want to order the routes in inverse order of path specificity. This is to attempt to match and render more specific paths first, then falling back to less specific paths.
Example:
<Switch>
<Route path="/segment1/segment2/page" component={....} />
<Route path="/segment1/page" component={....} />
<Route path="/segment1" component={....} />
</Switch>
Related
I've had two routes:
<Route path="/client/:id/members" component={Members} />
<Route path="/client/:id/members/:mid" component={MemberProfile} />
why when I'm trigger:
window.location.href = `/client/${id}/members/${mid}`;
then my url is changed to correct form like in the route path but not redirect me to MemberProfile component?
Thanks for any help!
as
<Route path="/client/:id/members" component={Members} />
declared before
<Route path="/client/:id/members/:mid" component={MemberProfile} />
and
/client/${id}/members/${mid}
fit
"/client/:id/members"
Members component will still be rendered.
Consider one of the following:
decleare MemberProfile before Members
change MembersProfile route to /client/:id/member/:mid for example
use exact Route property
Given:
<Route path="/client/:id/members" component={Members} />
<Route path="/client/:id/members/:mid" component={MemberProfile} />
It seems like you are rendering these routes into a Switch component. Remember that the Switch component renders the first child <Route> or <Redirect> that matches the location. This means that route path order and specificity matters.
Order your routes by decreasing specificity. "/client/:id/members/:mid" is more specific than "/client/:id/members" and should render higher in the Switch.
<Switch>
...
<Route path="/client/:id/members/:mid" component={MemberProfile} />
<Route path="/client/:id/members" component={Members} />
...
</Switch>
Additional note
You may want to avoid using window.location.href to redirect as this reloads the entire page, and thus, your app. Use a Redirect component to render a declarative navigation, or use history.replace(`/client/${id}/members/${mid}`); to issue an imperative redirect.
I'm trying to create an independent Route (not sure if that's the correct term) at BulletinBoard.js where I can use Link to go to Create Bulletin component.
I'm also trying to create another independent Route at BulletinList.js where I can navigate to Edit Bulletin with their respective IDs.
Before this, I tried using useRouteMatch with path and url, but apparently that wasn't the correct way to do it, now I'm told to use useLocation, it does adds the /createbulletin and /editbulletin/id paths behind the current URL, but it doesn't navigate to the component itself.
I've been cracking my head over this for the past 2 days and I still haven't figured out the correct way to do this.
Here is the codesandbox that I've created for reference.
The reason your code didnt navigate to a different component after the url changed is because you didnt use the exact attribute when declaring the route. So its matching /bulletinboard/anything and then it always renders de BulletinBoard component.
You could define all routes at the App.js file like
<Switch>
<Route path="/" component={Home} exact />
<Route path="/bulletinboard" component={BulletinBoard} exact />
<Route path="/bulletinboard/edit/:id" component={EditBulletinBoard} exact />
<Route path="/infohub" component={InfoHub} exact />
<Route component={NotFound} />
</Switch>
Also, check out the useHistory hook
So at the BulletinBoard.js when the user clicks the link
onClick={() => history.push(`/bulletinboard/edit/${id}`)}
Note that the edit route renders a different component that your codesandbox didn't have yet
I'm trying to do something like this
<Switch>
<SomeNavBar>
<Route path="page1">Page 1</Route>
<Route path="page2">Page 2</Route>
<Route path="page3">Page 3</Route>
</SomeNavBar>
<OtherNavBar>
<Route path="admin">Admin Page</Route>
</OtherNavBar>
</Switch>
Where I have wrapper component for a routes that are not the admin page.
However the admin route does not render Admin Page it just renders a blank page. The other routes work fine.
Is there a way to achieve this behavior?
There's a couple of issues with your example not related to the question which you should rectify before anything else.
The first is that the direct children of a Switch must always be a Route or Redirect - it doesn't know what to do with any other element and will just render the first thing it sees (in your case, the SomeNavBar component). The second is that path declarations must be prepended with a slash for the router to build them correctly, so /page1 and /admin for example.
With that out the way, here is a somewhat contrived example of how to get the behaviour you are after. For the pages, we are checking from a list of possible fragments before rendering SomeNavBar and the correct route. Notice also the exact parameter - this is so we don't also match paths that only begin with the specificed fragment, like /page1foo:
<Switch>
<Route exact path={['/page1', '/page2', '/page3']}>
<SomeNavBar>
<Route path="/page1">Page 1</Route>
<Route path="/page2">Page 2</Route>
<Route path="/page3">Page 3</Route>
</SomeNavBar>
</Route>
<Route path="/admin">
<OtherNavBar>
Admin Page
</OtherNavBar>
</Route>
</Switch>
I am creating routing for my react app, could someone explain me difference between these two approaches.
From user point of view they work the same, what is the difference in performance, best practice?
First one is multiple Routes rendering different component for the same path:
<Route path='/:shop/booking' component={Services}/>
<Route path='/:shop/booking' component={Calendar}/>
Second is single path rendering components as props.children(?) :
<Route path='/:shop/booking'>
<Aux>
<Services/>
<Calendar/>
</Aux>
</Route>
<Route path='/'>
<Component>
</Route>
Is equivalent to :
<Route path='/' children={Component}/>
According to this : https://reacttraining.com/react-router/core/api/Route/children-func :
Sometimes you need to render whether the path matches the location or
not. In these cases, you can use the function children prop. It works
exactly like render except that it gets called whether there is a
match or not.The children render prop receives all the same route
props as the component and render methods, except when a route fails
to match the URL, then match is null. This allows you to dynamically
adjust your UI based on whether or not the route matches.
So by giving children prop instead of component to your route, you force it to render even if the current URL does not match. And I might be mistaking but it seems that adding a component prop to a route override its children prop.
Thus you cannot expect the same behavior for this two pieces of code :
<Route path='/:shop/booking' component={Services}/>
<Route path='/:shop/booking' component={Calendar}/>
Shows the two components for the specified path.
<Route path='/:shop/booking'>
<Aux>
<Services/>
<Calendar/>
</Aux>
</Route>
Shows the two components wrapped in another, for any path.
Finally, I would say that the best practice in React is to wrap your two components into one, and add it to the component prop of a route instead of creating two routes with the exact same path.
If you cannot wrap your two components because one has to be displayed on several routes, you can use something like the following :
<BrowserRouter>
<div>
<Header />
<Switch>
<Route path='/' component={Home}/>
<Route path='/foo' component={Foo}/>
<Route path='/foo2' component={Foo2}/>
</Switch>
<Footer />
</div>
</BrowserRouter>
In the react (15.4.1) docs, it says that instead of using props.children for including components, it is also possible to use custom properties props.left and props.right to include multiple children in components with "holes".
How can this approach be realized when using React Router (3.0.0)? The component={MyComponent} syntax does not seem to allow including multiple children.
I want to able to write something like this:
<Router>
<Route component={App} >
<Route component={Page}>
<Route page='/' component={Header}> <-- 1st child of Page
<Route component={Logo} /> <-- 1st child of Header
<Route component={Nav} /> <-- 2nd child of Header
</Route>
<Route page='/' component={Home} /> <-- 2nd child of Page
<Route page='/custom' component={Custom} /> <-- 2nd child of Page, 2nd route
</Route>
</Route>
<Router>
I am aware that I can do the nesting within the components, but then I have various components imports spread accross my files, which increases dependencies and makes it hard to track which component is used where.
I would like to keep all component dependencies in the one file that defines the routing structure.