reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.88k stars 15.27k forks source link

mapStateToProps() not being called #1726

Closed richb-hanover closed 8 years ago

richb-hanover commented 8 years ago

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:

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

gaearon commented 8 years ago

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’s 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’ll 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’re 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’t even get called, it means that

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’re confident there is a bug in the library that you’re able to consistently reproduce with an example you can share.

Thanks!

markerikson commented 8 years ago

Answering in order:

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 .

marin101 commented 7 years ago

@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

madandrija commented 5 years ago

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

Or your reducer returned referentially identical value so connect() didn’t 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.