8

Edit: I was able to have the connected Child update on context change with the suggestion in the Redux troubleshooting of setting { pure: false } however I needed to add this to all of the connected Components that are parents of the Child I want to be update. Seems odd and inefficient to have to do this to a lot of components.


First of all, I will describe what my desired end result is before diving into what I think the problem is.

I am wanting to access the params of react router from within a grandchild component of my Route component. There are several references that say it is not necessary to sync the current route with the store if you are not using "recording, persisting, and replaying user actions, using time travel" so I have avoided it so far. Dan Abramov states here that it is okay to keep them un-synced and to just use the route as the source of truth.

Similar questions that didn't have answers to my problems: https://stackoverflow.com/a/36633892/2300773
Passing store through context with <Provider> not working in connect() based scenario

Since react-router does not provide the current params as context, I have opted to add the current param as a context from the Route component. I can access the context all the way down the component tree until I reach a Child that has been setup using connect(). The initial rendering of the page gives the Child the correct context. However, when the context changes, the Child does not get updated. connect() appears to be stripping away the context link.

Is this the desired behaviour of connect()? If so, how is a component supposed to access the route params without them being synced to the store?

Here is a JSFiddle showing a very minimal scenario where context isn't updated past the "connected" component.

Here is a bit of my actual code where I am sending the current route param as context from the Router component (/view/:viewSlug). I have tried setting the component to { pure: false } in connect as described in the redux troubleshooting but had no luck.

Parent (App) Component

class App extends Component {
  getChildContext() {
    const { viewSlug } = this.props

    return { viewSlug }
  }

  ...
}

App.PropTypes = {
  ...
}

App.childContextTypes = {
  viewSlug: PropTypes.string
}

...

export default connect(mapStateToProps)(App)

Grandchild (Day) Container

import Day from '../components/Day'

const mapStateToProps = (state, ownProps) => {
  ...
}

export default connect(mapStateToProps)(Day)

Grandchild (Day) Component

class Day extends Component {
  ...
}

Day.PropTypes = {
  ...
}

Day.contextTypes = {
  viewSlug: PropTypes.string.isRequired
}

export default Day
Community
  • 1
  • 1
Chad Fawcett
  • 246
  • 2
  • 10

3 Answers3

9

After a quick discussion with Dan Abramov, there doesn't appear to be a good way to pass params down to various deep components. (Discussion here). Once React Router 3.0 is released, this will be very trivial to implement correctly.

Workaround

Currently the only workaround I found was to set the Child Component, as well as any Ancestor Components between the Child Component and the Route Component, as an "un-pure" component.

This can be done with the options parameter when using the connect() function.

export default connect(mapStateToProps, null, null, { pure: false })(MyComponent)

This information was found on the Redux Troubleshooting page

Chad Fawcett
  • 246
  • 2
  • 10
1

I have the same problem: I have a language param in a route path which I store as context and use in all components of my app. And when I connect redux store, context properties are not updating anymore.

As you mentioned, there is a { pure: false } argument to force rerender the component more times than needed.

Another solution to not use context. You can use react-router-redux to save and update changed URL in Redux store. It has its own action LOCATION_CHANGE which has a payload object:

routing: {
    locationBeforeTransitions: {
        pathname: 'view/someView',
        search: '',
        …
    }
}

For now it does not have params, but you can parse pathname and get them in old school regexp way. :)

And please read this issue, there are some other workarounds: https://github.com/facebook/react/issues/2517

UPDATE:

There is another way — save your app state in redux store:

…
import { setCurLang } from '../actions/langActions';

const App = React.createClass({
    componentWillMount(){
        this.props.actions.setCurLang(this.props.params.curLang);
    },
    shouldComponentUpdate: function(nextProps) {
        return nextProps.params.curLang !== this.props.params.curLang;
    },
    componentWillUpdate(nextProps) {
        this.props.actions.setCurLang(nextProps.params.curLang);
    },
    render() {
        return (
            <div>
                {/* here you can use your saved URL-param as prop,
                    and it will be updated each time react router changes */}
                {this.props.curLang}
            </div>
        )
    }
});
function mapStateToProps(state) {
    return {
        curLang: state.curLang
    }
}
function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators( { setCurLang }, dispatch)
    }
}
export default connect(null, mapDispatchToProps)(App);

my reducer (this is simplified, of course you can use combineReducers here):

const curLang = 'de';

export default function reducer(state = curLang, action) {
    switch (action.type){
        case 'SET_CUR_LANG':
            return { ...state, cur: action.curLang }
        default:
            return state;
    }
}

and action:

export function setCurLang(curLang) {
    return {
        type: 'SET_CUR_LANG',
        curLang: curLang
    }
}
1

The reason why We won’t receive this.context when we use react-redux connect() is that we don't specify contextTypes check this comment

Add contextTypes like this:

import PropTypes from 'prop-types';

Myapp.contextTypes = { store: PropTypes.object };

Now, we can find that this.context has a value and the store is available on this.context.store

For more illustration check this comment also

Abdallah Okasha
  • 1,227
  • 14
  • 18