Redux: mapStateToProps() not being called

Created on 13 May 2016  路  4Comments  路  Source: reduxjs/redux

I've read tons of documentation - on all the popular sites - but nowhere have I found an answer. I'm asking here to see if I have missed some documentation, or a vital piece of understanding.

The problem I see is that my component's mapStateToProps() function is only called once (at initialization). It never is called again, even when the reducer gets called. I am beginning to think that it's because I don't understand how actions/reducers/props are all tied together.

Let me first say what I _do_ know:

  • I understand that actions describe updates to a portion (a property) of that store.
  • I understand that each reducer is responsible for updating its slice/portion of the store. (And I definitely understand that the reducer must return a new object for its slice.)
  • I understand that mapStateToProps "plucks up" certain data from the store and passes it as props to the named component.

But... I haven't found a description of how actions, reducers, and mapStateToProps all relate to each other. My questions:

  • I think of the store as being an object, with properties that hold data about the application's state. Those top-level properties can be objects that provide a sub-tree for holding info about a portion of the application. Is this true?
  • When an action is dispatched to the store, what property (or sub-tree of the store) gets updated? It seems there's an automatic creation of/association to a property within the store, but how is that determined?
  • How does mapStateToProps "know" to listen for updates to the proper property/part of the store? What ties them together?
  • How could I debug this? What would I need to do to figure out why dispatching the action doesn't seem to fire mapStateToProps?
  • Or am I thinking about it all wrong... ?
question

Most helpful comment

Have you gone through http://redux.js.org/docs/Troubleshooting.html and verified your case is not one of the issues it describes?

I think of the store as being an object, with properties that hold data about the application's state. Those top-level properties can be objects that provide a sub-tree for holding info about a portion of the application. Is this true?

Yea. Store holds the top-level state object internally. It provides access to that object from store.getState(). That object corresponds to whatever you return from the root reducer, and it鈥檚 often tree-like.

When an action is dispatched to the store, what property (or sub-tree of the store) gets updated? It seems there's an automatic creation of/association to a property within the store, but how is that determined?

No, there is no automatic creation of anything. The store just starts pointing to the result of calling your root reducer.

If your root reducer is a function you wrote yourself, its result is what you鈥檒l get after dispatching an action:

function counter(state = 0, action) {
  if (action.type === 'INCREMENT') {
    return state + 1; // will become the next value of store.getState() after store.dispatch()
  }
  return state;
}

If you _generate_ the root reducer with something like combineReducers({ foo, bar }), it is the same as if you wrote a root reducer by hand that looks like this:

function root(state = {}, action) {
  return {
    foo: foo(state.foo, action),
    bar: bar(state.bar, action)
  }
}

This is where the properties on the state often come from. Their values are determined by what your foo and bar reducers returned while handling the action, respectively, so again you鈥檙e in full control over them.

How does mapStateToProps "know" to listen for updates to the proper property/part of the store? What ties them together?

You pass mapStateToProps to connect() function. It generates a component that uses store.subscribe() to listen to every change to the store. When the store has changed, the generated component reads store.getState() and passes it to mapStateToProps() to determine the next props to passed down. If they changed, it will re-render your component with them.

How could I debug this? What would I need to do to figure out why dispatching the action doesn't seem to fire mapStateToProps?

Add a middleware like https://github.com/theaqua/redux-logger. Verify that the state updates after the action in the way you expect. If mapStateToProps doesn鈥檛 even get called, it means that

  • Either you forgot to dispatch() an action and just called an action creator,
  • Or your reducer returned referentially identical value so connect() didn鈥檛 bother calling mapStateToProps().

Both of these cases are described in http://redux.js.org/docs/Troubleshooting.html.

In the future, we ask that you try to ask questions on StackOverflow first, and use the issue tracker when you鈥檙e confident there is a bug in the library that you鈥檙e able to consistently reproduce with an example you can share.

Thanks!

All 4 comments

Have you gone through http://redux.js.org/docs/Troubleshooting.html and verified your case is not one of the issues it describes?

I think of the store as being an object, with properties that hold data about the application's state. Those top-level properties can be objects that provide a sub-tree for holding info about a portion of the application. Is this true?

Yea. Store holds the top-level state object internally. It provides access to that object from store.getState(). That object corresponds to whatever you return from the root reducer, and it鈥檚 often tree-like.

When an action is dispatched to the store, what property (or sub-tree of the store) gets updated? It seems there's an automatic creation of/association to a property within the store, but how is that determined?

No, there is no automatic creation of anything. The store just starts pointing to the result of calling your root reducer.

If your root reducer is a function you wrote yourself, its result is what you鈥檒l get after dispatching an action:

function counter(state = 0, action) {
  if (action.type === 'INCREMENT') {
    return state + 1; // will become the next value of store.getState() after store.dispatch()
  }
  return state;
}

If you _generate_ the root reducer with something like combineReducers({ foo, bar }), it is the same as if you wrote a root reducer by hand that looks like this:

function root(state = {}, action) {
  return {
    foo: foo(state.foo, action),
    bar: bar(state.bar, action)
  }
}

This is where the properties on the state often come from. Their values are determined by what your foo and bar reducers returned while handling the action, respectively, so again you鈥檙e in full control over them.

How does mapStateToProps "know" to listen for updates to the proper property/part of the store? What ties them together?

You pass mapStateToProps to connect() function. It generates a component that uses store.subscribe() to listen to every change to the store. When the store has changed, the generated component reads store.getState() and passes it to mapStateToProps() to determine the next props to passed down. If they changed, it will re-render your component with them.

How could I debug this? What would I need to do to figure out why dispatching the action doesn't seem to fire mapStateToProps?

Add a middleware like https://github.com/theaqua/redux-logger. Verify that the state updates after the action in the way you expect. If mapStateToProps doesn鈥檛 even get called, it means that

  • Either you forgot to dispatch() an action and just called an action creator,
  • Or your reducer returned referentially identical value so connect() didn鈥檛 bother calling mapStateToProps().

Both of these cases are described in http://redux.js.org/docs/Troubleshooting.html.

In the future, we ask that you try to ask questions on StackOverflow first, and use the issue tracker when you鈥檙e confident there is a bug in the library that you鈥檙e able to consistently reproduce with an example you can share.

Thanks!

Answering in order:

  • Redux generally assumes that the top piece of your state tree is a plain Javascript object (although it's also common to use the data types provided by the Immutable.js library). What you put inside of that object and how you structure the contents is entirely up to you. The most common approach is to organize things by domain. For example, a state tree for a hypothetical blog app might look like: { users : {}, posts : {}, comments : {}, ui : {} }. See http://redux.js.org/docs/FAQ.html#reducers-share-state and http://redux.js.org/docs/FAQ.html#organizing-state-nested-data.
  • Technically speaking, when you create your store you define a _single_ "reducer" function. That function is called with the current state and the incoming action, and responsible for returning a new state object, with _all_ appropriate updates, done in an immutable fashion (ie, no direct in-place edits - everything that needs to be updated should be done by making copies, modifying them, and returning the copies instead, and updating a nested field means also copying and updating all of its ancestors). However, since putting every last bit of update logic into one giant function is a bad idea, that update logic is almost always broken into smaller reusable sub-reducer functions. How you structure that logic is again up to you, but nothing gets updated automatically - it's up to your reducer logic to do that. The combineReducers utility that comes with Redux makes it easy to have a specific reducer function that is responsible for managing updates to a given slice of data, like: const combinedReducer = combineReducers({users : userReducer, posts : postReducer, comments : commentsReducer, ui : uiReducer});
  • Redux simply notifies all subscribers whenever _any_ action is run through the store. The React Redux connect() function subscribes, and then calls the component's mapStateToProps to see if any of the extracted data has changed since the last time. There's no specific nested state subscription. See http://redux.js.org/docs/FAQ.html#store-setup-subscriptions.
  • The number one cause of a component not updating is direct mutation of state in your reducer. See http://redux.js.org/docs/FAQ.html#react-not-rerendering . Beyond that, there's a number of addons you can use to debug and log actions. I have a bunch of them listed over at https://github.com/markerikson/redux-ecosystem-links/blob/master/devtools.md.

Hopefully that helps clear things up.

Beyond that, as Dan said: usage questions are generally better suited for Stack Overflow. Also, the Reactiflux community on Discord has a bunch of chat channels dedicated to React-related technologies, including Redux, and there's always a number of people hanging around willing to answer questions. There's an invite link at https://www.reactiflux.com .

@richb-hanover You've probably witnessed the shallow compare which is performed by the react-redux connect. This means that objects nested within other objects won't result in rerender even if they are modified because only the root key are checked for modifications.

http://redux.js.org/docs/faq/ReactRedux.html#why-isnt-my-component-re-rendering-or-my-mapstatetoprops-running

@richb-hanover In my case, @gaearon detected the issue:

Or your reducer returned referentially identical value so connect() didn鈥檛 bother calling mapStateToProps().

I was using this fix and this was the resulting code

const mapStateToProps = (state, props) => {
    return { id: props.id, currentState: state[props.id] };
}

const mapDispatchToProps = (dispatch) => {
    return {
        increment: (id) => {
            dispatch({ type: 'INCREMENT', id: id })
        }
    }
}

const DumbListItem = ({
    increment,
    id,
    currentState
}) => (
        <div>
            <li onClick={() => increment()}>{id}</li>
            <span>{currentState}</span>
        </div>
    );

export const ConnectedListItem = connect(
    mapStateToProps,
    mapDispatchToProps
)(DumbListItem);

Since I didn't pass the id parameter to the increment call (either as increment(id) in the template, or via the second parameter in mapDispatchToProps), the store didn't update correctly so do check if perhaps that's your issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rui-ktei picture rui-ktei  路  3Comments

mickeyreiss-visor picture mickeyreiss-visor  路  3Comments

olalonde picture olalonde  路  3Comments

dmitry-zaets picture dmitry-zaets  路  3Comments

amorphius picture amorphius  路  3Comments