wildlifehexagon
wildlifehexagon

Reputation: 523

OwnProps not being passed with React Router V4

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

Answers (2)

Zac Collier
Zac Collier

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

lemarc
lemarc

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

Related Questions