Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mapStateToProps() not being called #1726

Closed
richb-hanover opened this issue May 13, 2016 · 4 comments
Closed

mapStateToProps() not being called #1726

richb-hanover opened this issue May 13, 2016 · 4 comments
Labels

Comments

@richb-hanover
Copy link

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... ?
@gaearon
Copy link
Contributor

gaearon commented May 13, 2016

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

  • Either you forgot to dispatch() an action and just called an action creator,
  • Or your reducer returned referentially identical value so connect() didn’t 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’re confident there is a bug in the library that you’re able to consistently reproduce with an example you can share.

Thanks!

@markerikson
Copy link
Contributor

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 .

@marin101
Copy link

marin101 commented Jan 20, 2017

@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
Copy link

madandrija commented Nov 6, 2018

@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.

@reduxjs reduxjs deleted a comment from shures Apr 4, 2019
@reduxjs reduxjs locked as resolved and limited conversation to collaborators Apr 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants