Reputation: 523
I'm refactoring an app from V2 to V4 of React Router, and I have run into an issue where properties from my mapStateToProps
are not being passed down to each component. Here's my setup:
App.jsx
render() {
return (
<div>
<Header />
<Navbar />
<Routes {...this.props} />
<Footer />
</div>
)
}
const mapStateToProps = (state, ownProps) => {
const { page } = state
return {
routeProps: ownProps,
page: page
}
}
const mapDispatchToProps = (dispatch) => {
return {
pageActions: bindActionCreators(pageActionCreators, dispatch),
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
Routes.jsx has this line:
<Route exact path="/:name/information" render={ ({ match }) => <InfoPage {...this.props} match={ match } /> } />
InfoPage.jsx
componentDidMount() {
const { routeProps, pageActions } = this.props
pageActions.fetchInfoPage(routeProps.match.params.name)
}
componentWillReceiveProps(nextProps) {
if (this.props.page.page !== nextProps.page.page) {
const { routeProps, pageActions } = nextProps
pageActions.fetchInfoPage(routeProps.match.params.name)
}
}
render() {
return (
<InfoPageView
page={ this.props.page }
pageActions={ this.props.pageActions }
routeProps={ this.props.routeProps }
/>
)
}
The problem is that when I try to access a route that uses InfoPage, i.e. /orders/information
, it never loads. When I check Redux dev tools, it says that page
and content
are undefined. The only things I changed on the InfoPage component while refactoring were adding in match.params
instead of params
. This is leading me to believe that there is something wrong with my Redux/Router integration, specifically with ownProps/routeProps
not being passed down properly.
Any ideas as to what's wrong here?
Upvotes: 1
Views: 1872
Reputation: 160
First, ensure that you're wrapping Routes.jsx
(as @lemarc and @Kyle Richardson alluded to earlier) so that your routed components will update properly.
When you wrap a connected component with withRouter
, it will make match
available in that component's props.
Instead of trying to map match
via ownProps
, you can just access it from your wrapped component's props
.
Also, it looks like you're trying to reference Action Creators which are bound to your App
component from your child component InfoPageView
. This is an anti-pattern in React + Redux apps.
If your InfoPageView
needs the ability to dispatch an Action, you should provide it to InfoPageView
directly via its own mapDispatchToProps
function. The same goes for the page
property you're trying to access; if InfoPageView
needs to read the value of page
, you should provide it directly via mapStateToProps
:
import { Component } from 'react'
import { connect, bindActionCreators } from 'react-redux'
import { withRouter } from 'react-router'
import { fetchInfoPage as fetchInfoPageAction } from '../actions/fetchInfoPage'
// ^ import directly from wherever this Action Creator resides in your project ^
class InfoPageView extends Component {
componentDidMount() {
const { match } = this.props
fetchInfoPage(match.params.name) // <-- `match` provided by withRouter()
}
componentWillReceiveProps(nextProps) {
if (this.props.page.page !== nextProps.page.page) {
const { match, fetchInfoPage } = nextProps
fetchInfoPage(match.params.name) // <-- `match` provided by withRouter()
}
}
render() {
return (
<InfoPageView />
)
}
}
const mapStateToProps = (state) => ({
page: state.page,
content: state.content,
})
const mapDispatchToProps = (dispatch) => ({
fetchInfoPage: bindActionCreators(fetchInfoPageAction, dispatch),
})
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(InfoPageView)
)
If, after all that, you find that page
and content
are still undefined
, it's likely that they're simply missing from your page
reducer. Verify that those properties are present in your page
reducer's initialState
:
const initialState = { page: 1, content: 'initial content' }
const page = (state = initialState, action) => ({
switch (action.type) {
// (action type `case` statements here)
default:
return state
}
})
export default page
...and also verify that you're passing the page
reducer to your store via combineReducers
:
import pageReducer from '../reducers/page'
const rootReducer = combineReducers({
page: pageReducer,
})
const store = configureStore(
rootReducer,
/* store middlewares, etc */
)
export default store
Upvotes: 2
Reputation: 121
Could you try moving your withRouter
call to Routes.jsx
export default withRouter(connect()(Routes))
I believe that needs to be in the same file as any <Route ... />
components.
Upvotes: 2