I'm building an app with React, React-Router (v5) and Redux and wonder how to access the params of the current URL in a parent route.
That's my entry, the router.js
:
<Wrapper>
<Route exact path="/login" render={(props) => (
<LoginPage {...props} entryPath={this.entryPath} />
)} />
<Route exact path="/" component={UserIsAuthenticated(HomePage)} />
<Route exact path="/about" component={UserIsAuthenticated(AboutPage)} />
<Route path="/projects" component={UserIsAuthenticated(ProjectsPage)} />
</Wrapper>
And that's my ProjectsPage
component:
class ProjectsPage extends PureComponent {
componentDidMount() {
this.props.fetchProjectsRequest()
}
render() {
console.log(this.props.active)
if (this.props.loading) {
return <Loading />
} else {
return (
<Wrapper>
<Sidebar>
<ProjectList projects={this.props.projects} />
</Sidebar>
<Content>
<Route exact path="/projects" component={ProjectsDashboard} />
<Switch>
<Route exact path="/projects/new" component={ProjectNew} />
<Route exact path="/projects/:id/edit" component={ProjectEdit} />
<Route exact path="/projects/:id" component={ProjectPage} />
</Switch>
</Content>
</Wrapper>
)
}
}
}
const enhance = connect(
(state, props) => ({
active: props.match,
loading: projectSelectors.loading(state),
projects: projectSelectors.projects(state)
}),
{
fetchProjectsRequest
}
)
export default withRouter(enhance(ProjectsPage))
The problem is, that the console.log
output in my render
method is {"path":"/projects","url":"/projects","isExact":false,"params":{}}
although the URL is http://localhost:3000/projects/14
.
I want to add an ID
prop to my ProjectList
to highlight the currently selected project.
I could save the ID of the project in a store inside my ProjectPage
component, but I think this would be a bit confusing, especially because the URL has the information actually – so why should I write something in the store?
Another (bad?) approach would be to parse the location object the get the ID by myself, but I think there is a react-router
/react-router-redux
way to get the params at this point that I've overlooked.
@Kyle explained issue very well from technical perspective.
I will just focus on solution to that problem.
You can use matchPath
to get id
of selected project.
matchPath - https://reacttraining.com/react-router/web/api/matchPath
This lets you use the same matching code that uses except
outside of the normal render cycle, like gathering up data
dependencies before rendering on the server.
Usage in this case is very straight forward.
1 Use matchPath
// history is one of the props passed by react-router to component
// @link https://reacttraining.com/react-router/web/api/history
const match = matchPath(history.location.pathname, {
// You can share this string as a constant if you want
path: "/articles/:id"
});
let articleId;
// match can be null
if (match && match.params.id) {
articleId = match.params.id;
}
2 Use articleId
in render
{articleId && (
<h1>You selected article with id: {articleId}</h1>
)}
I build a simple demo which you can use to implement the same functionality in your project.
Demo: https://codesandbox.io/s/pQo6YMZop
I think that this solution is quite elegant because we use official react-router
API which is also used for path matching in router. We also don't use window.location
here so testing / mocking will be easy if you export also raw component.
TLDR
React router match.params
will be an empty object if your <Route />
path
property doesn't include :params
.
Solve this use case: You could use let id = window.location.pathname.split("/").pop();
in your parent route's component to get the id.
Detailed reason why not
If the path
prop supplied to a <Route />
doesn't have any params, such as /:id
, react router isn't going to doing the parsing for you. If you look in matchPath.js
at line #56 you can start to see how the match
prop is constructed.
return {
path: path, // the path pattern used to match
url: path === '/' && url === '' ? '/' : url, // the matched portion of the URL
isExact: isExact, // whether or not we matched exactly
params: keys.reduce(function (memo, key, index) {
memo[key.name] = values[index];
return memo;
}, {})
};
Then we can look at line #43 you can see that keys
comes from _compilePath.keys
. We can then look at the compilePath
function and see that it uses pathToRegexp()
, which will use stringToRegexp()
, which will use tokensToRegExp()
which will then mutate keys
on line #355 with keys.push(token)
. With a <Route />
that has no params value in its path
prop, the parse()
function used in stringToRegexp()
will not return any tokens and line #355 won't even be reached because the only tokens array will not contain any token objects.
So.... if your <Route />
doesn't have :params
then the keys value will be an empty array and you will not have any params on match
.
In conclusion, it looks like you're going to have to get the params yourself if your route isn't taking them into account. You could to that by using let id = window.location.pathname.split("/").pop()
.