Since path-to-regexp accepts arrays in addition to strings for the pattern argument, I think it would follow for React Router to be able to accept the prop without issuing a warning. Passing an array for path works fine in the latest beta in terms of matching appropriately, you just get a prop types warning.
This has been brought up before (#4159) and rejected, but I think it's possible with the v4 design. The only question I have looking at the source is how you would approach caching the match. Maybe just not caching if the pattern is an array? Or possibly stringifying the array and using that for the key? I'm not sure what the realized perf difference would be.
Also, I built a route patterns utility a couple weeks ago when tinkering around with this stuff https://lourd.github.io/route-patterns-utility/. You might find it helpful for some interactive feedback on what the input and output to path-to-regexp is.
Exactly what I needed today.
A simple example of this: a set of exact routes, that have a common part.
const userRoutes = [
{
path: '/users/create',
component: UsersCreate
},
{
path: '/users/:id',
component: UsersShow
},
{
path: '/users/:id/edit',
component: UsersEdit
}
]
const UserRoutes = () => (
<section>
{/* Common part */}
<Route exact path={['/users', ...userRoutes.map(route => route.path)]} component={UsersIndex} />
{/* Part, that differs */}
<Switch>
{userRoutes.map(route => (
<Route key={route.path} exact path={route.path} component={route.component} />
))}
</Switch>
</section>
)
Not sure how to prevent the common UsersIndex component from rerendering on route change though. Maybe just use shouldComponentUpdate inside of it 馃
or this way:
const userRoutes = [
{
path: '/users/create',
component: UsersCreate
},
{
path: '/users/:id',
component: UsersShow
},
{
path: '/users/:id/edit',
component: UsersEdit
}
]
const UserRoutes = () => (
<Route
exact
path={['/users', ...userRoutes.map(route => route.path)]}
render={() => (
<section>
<UsersIndex />
<Switch>
{userRoutes.map(route => (
<Route key={route.path} exact path={route.path} component={route.component} />
))}
</Switch>
</section>
)}
/>
)
@lourd FWIW, I rejected that because it was attempting to make a non-trivial change into a trivial one. This needs changes (and tests!) in other parts of the code to make it work. If someone wants to put together that PR, I'll give it a look.
A few thoughts:
Map would work, but then that would have to be polyfilled.path-to-regexp also supports paths that are regular expressions. Should those be supported as well? I think that those would have to be absolute.@VladShcherbin Glad to hear it's hitting a use case for you!
@timdorr, I understand. What other parts of the code do you suggest looking at aside from that in matchPath?
@pshrmn, yeah I agree on a Map being best suited for a cache. The library could have progressive enhancement for caching, checking for the presence of Map, falling back to an object literal and not caching paths that are arrays.
I'm sure some people have use cases for giving regular expressions as paths, but that functionality doesn't necessarily need to be intertwined in supporting arrays.
+1 Yea, it could be a nice feature.
I was looking for a solution to display elements for certain pages. Looping <Route> do the trick, but feels not good at all. Passing array to path looks way better.
We don't need to support every feature of path-to-regexp. I'd say just map over your routes and generate a <Route> element for each one.
I'm 馃憥 on this. You can compose to get this API if you want it.
const RouteWithPaths = ({ paths, ...rest }) => (
<Switch>
{paths.map(path => (
<Route path={path} {...rest}/>
))}
</Switch>
)
<RouteWithPaths paths={[ ... ]} component={Whatever}/>
Looping
do the trick, but feels not good at all.
Why? Count up how many times you loop over an array and render an element in your app, it's everywhere, it's how React works 馃槏
@ryanflorence Okay, here is the use-case:
We have two routes - /users and /users/create.
Out UI is divided into two parts: users list and users create form. We want to always show users list.
If we use this code:
// current proposed routes configuration
const Routes = () => (
<BrowserRouter>
<section>
<ul>
<li><Link to="/users">users</Link></li>
<li><Link to="/users/create">users create</Link></li>
</ul>
<hr />
<Switch>
<Route exact path="/users" render={() => <UsersIndex />} />
<Route exact path="/users/create" render={() => <UsersIndex />} />
</Switch>
<Route exact path="/users/create" component={UsersCreate} />
</section>
</BrowserRouter>
)
// UsersIndex component is PureComponent to prevent re-rendering
class UsersIndex extends React.PureComponent {
render() {
console.log('render users list')
return <p>users list component</p>
}
}
Every time route is changing, the UsersIndex component will re-render because exact prop will trigger different routes. We can't remove exact because we don't want to show it on every page.
If we use an array of routes, re-rendering won't be triggering and it will look cleaner. This is already working, but gives a propTypes warning:
<Route exact path={['/users', '/users/create']} render={() => <UsersIndex />} />
<Route exact path="/users/create" component={UsersCreate} />
Where is the user's list? I think you just want some nesting.
If you want persistent UI, just render it:
const App = () => (
<BrowserRouter>
<div>
<ul>
<li><Link to="/users">users</Link></li>
<li><Link to="/users/create">users create</Link></li>
</ul>
<Route path="/users" render={({ match }) => (
<div>
<UsersIndex/>
<Route path={`${match.url}/create`} component={UsersCreate}/>
</div>
)}/>
</div>
</BrowserRouter>
)
@ryanflorence I tried this, but it will render on all /users routes, for example /users/yolo or smth else.
That is why I want to use exact and looping UsersIndex component with exact makes it re-render, even with shouldComponentUpdate.
do you have any mockups of what your app is supposed to look like at different urls? even some ascii box drawings with http://asciiflow.com/ would be great. The solution will be simple, but I'm not understanding what you want.
Wait:
Every time route is changing, the UsersIndex component will re-render
It's not unmounting, but just re-rendering?
@ryanflorence yes, it's mounting and unmounting on every route change.
I want it to mount only once and render on some exact routes. It's a list of users and routes are like modals.
/users route (UsersIndex is visible, UsersCreate form is not)

/users/create route (UsersIndex is visible, UsersCreate form is visible)

Indeed it does:
http://codepen.io/ReactJSTraining/pen/ZLggog
I'd consider that a bug, I'd expect switch to render UsersIndex each time but just pass it new props (the new match/location)
@ryanflorence yeah, that's what I am trying to do without re-mounting / re-rendering.
Here is the same codepen example with path array, UsersIndex is mounting only once:
Here's a new use case in v4 that I don't see a good workaround for:
I have a settings page, at /settings. One of the things you can do from the settings page is edit a set of things. So the user may click on one of these things and edit it at /things/123. There is currently no other navigation path to /things/123.
Using <Route children>, I want to keep the main navigation tab highlighted at "Settings", because that's what they're doing - editing a setting. With only a single path passed to <Route>, it appears that the only way to accomplish this is to use the path /settings/things/123, which is an unreasonable imposition.
Everything seems to work well enough by passing in a regex: <Route path="(/settings|/things)">. However, this seems less desirable than just passing in an array of strings.
Is there some other approach that should be taken with v4? If this message isn't clear enough, I'm happy to post code snippets.
For example, I have a sidebar menu item:
<Route
path={/\/(users|user-roles)(\/(\d+|new)\/edit)?/}
exact={true}
children={({ match }) => {
return (
<li className={classnames({ active: match })}>
<Link to="/users">Users</Link>
</li>
)
}}
/>
I want <li> to be highlighted on any of /users, /user-roles, /users/:id/edit, /user-roles/:id/edit routes match.
Maybe there is a different way to achieve this with current router.
By the way, it still works, but outputs warning
Invalid prop
pathof typeregexpsupplied toRoute, expectedstring.
Tell me if this is a lost cause, but I think there are valid use cases for regex/array support.
In my case I have three 'levels' of routing:
App level, which always renders and always has a child Navigation
Dashboard level, which is rendered on, say /, /todos and /projects but not on /chat or anything else. Dashboard always has children AccountSummary and Newsfeed.
Resource-specific children within Dashboard. So...
-- on /, we might show recommended projects (or whatever),
-- on /todos a list of todos,
-- on /todos/:id a specific todo,
-- on /projects a grid of projects,
-- on /project/:id/edit a modal for editing project details.
-- etc. etc. etc.
Here's a little ASCII to illustrate the structure

I expect my user to be jumping around a lot between routes for the Resource-specific children. I don't want to re-render Dashboard every time this happens.
My understanding is that:
<Route path={/\/($|todos|projects)/} component={Dashboard} />
will not trigger a re-render of Dashboard when these routes change but:
<Route path="/" component={Dashboard} />
<Route path="/todos" component={Dashboard} />
<Route path="/projects" component={Dashboard} />
will trigger a re-render of Dashboard.
If that is true, I'd love to avoid the rerender by using the regex (or array) for the path.
As people note above, this works but I get the PropTypes warning.
I'm in the exact same boat so a huge +1. I have four page routes, but three headers to display. One is shared between two of the pages. I had something like:
<Switch>
<Route path={/(a|b)/} component={SharedHeader} />
<Route path="c" component={HeaderC} />
<Route path="d" component={HeaderD} />
</Switch>
<Switch>
<Route path="a" component={BodyA} />
<Route path="b" component={BodyB} />
<Route path="c" component={BodyC} />
<Route path="d" component={BodyD} />
</Switch>
That seemed to work great until I ran into the prop types issue.
It seems like the suggested workflow otherwise is to just do the following?:
<Route path="a" component={SharedHeader} />
<Route path="b" component={SharedHeader} />
I'm using v. 4.2.2 and passing an array works as expected, but I get a console warning Failed prop type: Invalid prop 'path' of type 'array' supplied to 'Route', expected 'string'.
I'm with @JemBijoux above. Having <Route path={[a, b]} component={SharedHeader} /> is much more elegant than needing to map
{paths.map(path => (
<Route path={path} component={SharedHeader} />
))}
It's odd, because passing an array renders as expected, but I still get a console warning.
@marquizzo I have the same issue, do you find a workaround?
@shai32 There are several workarounds that are sloppy and sub-optimal. These are the two I've used:
Passing an array renders as expected, you just have to ignore the console log warning:
<Route path={[a, b]} component={SharedComponent} />
If you want to keep your <Switch> component clean of .map() functions, you could check the props.location.pathname within SharedComponent instead and only render it when it matches your desired path.
function SharedComponent(props){
const path = props.location.pathname;
if (path !== 'desired/path/one' && path !== 'desired/path/two') {
return null;
}
return (<div />);
}
Any update for a native support of array of string for the path props ?
Just chiming in here, like others in this thread I wanted my <Route> to match multiple paths to one component without remounting that component when the matched route changes.
This works perfectly when I provide my route with an array of paths to its' path prop, with the exception of the PropTypes warning Failed prop type: Invalid prop `path` of type `array` supplied to `Route`, expected `string`..
Unless I'm mistaken, is this issue not simply a case of swapping the line
path: PropTypes.string for path: PropTypes.oneOfType([PropTypes.string, PropTypes.array])?
This is something we want to support, but it just needs a solid implementation. Let's please continue the discussion in #5866.
Most helpful comment
Tell me if this is a lost cause, but I think there are valid use cases for regex/array support.
In my case I have three 'levels' of routing:
Applevel, which always renders and always has a childNavigationDashboardlevel, which is rendered on, say/,/todosand/projectsbut not on/chator anything else.Dashboardalways has childrenAccountSummaryandNewsfeed.Resource-specific children within
Dashboard. So...-- on
/, we might show recommended projects (or whatever),-- on
/todosa list of todos,-- on
/todos/:ida specific todo,-- on
/projectsa grid of projects,-- on
/project/:id/edita modal for editing project details.-- etc. etc. etc.
Here's a little ASCII to illustrate the structure
I expect my user to be jumping around a lot between routes for the Resource-specific children. I don't want to re-render
Dashboardevery time this happens.My understanding is that:
will not trigger a re-render of
Dashboardwhen these routes change but:will trigger a re-render of
Dashboard.If that is true, I'd love to avoid the rerender by using the regex (or array) for the path.
As people note above, this works but I get the PropTypes warning.