Redux: Components not re-rendering with connect()

Created on 20 Aug 2015  ·  32Comments  ·  Source: reduxjs/redux

I'm getting to know Redux by having read the docs and am starting out just modifying the real-world example. In my case, I've swapped the GitHub HTTP API calls for my own, and swapped the Repo and User concepts with analogous ones in my environment. So far, shouldn't be too different, right?

Here's where I'm running into trouble:

My API calls are succeeding, response JSON is getting camelCased and normalized, _but_ my component is not re-rendering after the API request happens successfully. Shortly after USER_REQUEST gets dispatched and the User component updates to show "Loading...", USER_SUCCESS gets dispatched, and its next state does contain a populated entities key:

pasted_image_8_19_15__3_27_pm

That would mean the problem is in the middleware, right? I should be storing the entities from the action in the store, and the component should observe the change and re-render, yes?

Where, in the real-world example, does successfully retrieved and normalized data _actually_ get put into the store? I see where it's being normalized, but not where the result of that normalization is being delivered to the store.

Thanks so much!

question

Most helpful comment

Data gets set/updated/deleted in the store via the results of handling actions in reducers. Reducers receive the current state of a slice of your app, and expect to get new state back. One of the most common reasons that your components might not be re-rendering is that you're modifying the existing state in your reducer instead of returning a new copy of state with the necessary changes (check out the Troubleshooting section). When you mutate the existing state directly, Redux doesn't detect a difference in state and won't notify your components that the store has changed.

So I'd definitely check out your reducers and make sure you're not mutating existing state. Hope that helps!

All 32 comments

Data gets set/updated/deleted in the store via the results of handling actions in reducers. Reducers receive the current state of a slice of your app, and expect to get new state back. One of the most common reasons that your components might not be re-rendering is that you're modifying the existing state in your reducer instead of returning a new copy of state with the necessary changes (check out the Troubleshooting section). When you mutate the existing state directly, Redux doesn't detect a difference in state and won't notify your components that the store has changed.

So I'd definitely check out your reducers and make sure you're not mutating existing state. Hope that helps!

In addition to what @ernieturner said, make sure that the mapStateToProps function in your connect(mapStateToProps)(YourConnectedComponent) function maps the relevant state (in this case entities) so that the connected component is listening to that slice of the state.

Also, keep in mind that connect() does a shallow comparison: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

It's likely that because the shape of your connected state didn't change, it's assuming to not do an update.

Ah, got it! My mapStateToProps was attempting to extract an object from state, but wasn't specifying the correct key. Instead of extracting entities.users[login], I had it set to entities.users[id]. The entities.users object was keyed by login string, so when the numeric id wasn't found, no props actually changed, so no re-render was needed.

Thanks, all, for the help! I really appreciate the time you took to assist.

@timdorr
That's ridiculous. How to force connect() check deep changes? Otherwise one has to create dozens of container components for every deeper level of state. It is not normal

You absolutely should have many connected components in any reasonably-sized app. If you have one single top level connected component, you are going to be re-rendering large parts of your app unnecessarily every time state changes at all. And you'll be obviating one of the primary optimizations of react-redux, which is to only re-render when the state it is subscribed to changes.

If you need to evaluate a complex query of state, then use reselect to speed that up.

@timdorr @wzup

It may be helpful to clarify that connecting at lower places in the tree is not strictly necessary to solve the deep comparison issue.

Connecting at lower places in your component tree may indeed help with _performance_, but it is not the fundamental solution to the deep comparison problem. The solution to deep comparison problems is for your reducers to update state immutably, so that a shallow comparison is adequate for knowing when a deeper piece of state changed.

In other words, connecting a top-level component to a large nested piece of state works just fine _even with shallow comparisons_, as long as your reducers return new state without mutating existing state objects.

@naw Thanks for that tip. Based on it I ended up adding a new reducer where I was experiencing this problem:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

This still feels weird to me, though. If I've got a reducer that goes:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

the fact that I'm returning a newly initialized object that just happens to be the same shape as the previous state should be enough to trigger a previousState !== newState. If I'm supposed to be returning immutable data each time, it seems like it's more inefficient to be doing a shallow comparison of the shape rather than just the identity. (Assuming that's what Redux is actually doing, that is. The fact that my Symbol workaround works makes me think that's what's going on)

In this particular case adding more container components doesn't solve the issue. Sadly I can't link to a whole github project for clarification because this is my company's closed source stuff I'm dealing with here.

@Zacqary The comparison of current state to previous state is done with strict equal (===). The comparison of stateProps (the result of mapStateToProps) is done with shallow equal.

@Zacqary

mapStateToProps is "assembling" data for your connected component by reaching into the store.

mapStateToProps pulls various piece(s) out of the store, and assigns them to keys in a new stateProps object.

The resulting statePropsobject will be new every time (so its identity changes), so we can't just compare the object identity of the stateProps object itself --- we _have_ to go down one level and check the object identity of each value, which is where shallowEqual comes into play, as @jimbolla said.

Typically problems arise when you write reducers in such a way that the object identity of a piece of state _doesn't change_, while some nested attribute within it _does_ change. This is mutating state rather than returning new state immutably, which causes react-redux to think nothing changed, when in fact, something _did_ change.

the fact that I'm returning a newly initialized object that just happens to be the same shape as the previous state should be enough to trigger a previousState !== newState.

If you return a new object for some slice of state, and mapStateToProps uses that slice of state as the value for one of its keys, then react-redux will see that the object identity changed, and will not prevent a re-render.

Is there something specific that isn't working as you expect it to? I'm not really sure what you mean by your Symbol workaround.

It's worth considering whether the additional component should simply be moved down into the connected() component to inherit the props. I got to this issue by attempting to connect() two components, but that was unnecessary complexity.

@ernieturner I know this is old but you might want to update your Troubleshooting Section link.

Cheers

I had this problem, well not exactly.
I applied the properties to state, like:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

And it wasn't working. So I applied value directly from props like

{this.props.text}

And it works just fine :)

@AlexanderKozhevin Unless I am mistaken, your constructor is missing a super(props).
Normally you're not even allowed to use this before you call super(props).

@cdubois-mh Right you are, thanx for note.

I've written custom-made app root reducer and get that problem.
Returning a copy of whole state
{...state}
in the end of the root reducer helped for me

@daedalius That's how a reducer is supposed to work.
It should return the state if the action is irrelevant, or return a copy of the state with the expected modified values.

If you don't return a copy, then react won't always be able to detect that a value changed.
( for example, if you modify a nested entry )

Check this state:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

If you modify the name of a client, but don't return a clone of the state, then you didn't modify the state, you modified the object in the array.

The object itself will still have the same reference as before, so react will miss the change.
By returning a clone, the state itself's reference will now be different ( since it's a new object ) and react will initiate the whole re-rendering process.

If I am wrong, please correct me, but this is how I understand reducers to work.

Yep, that's correct. Also, @daedalius , note that you don't want to _always_ make a shallow copy of state - you should only do that if something changed, otherwise you may wind up with unnecessary re-rendering of your components.

force update

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee What is that supposed to mean? you didn't provide any information or explanation of your piece of code. What does it do? Why is it relevant to the current discussion at hand?

@CedSharp The shallowEqual function will return false, if we set _forceUpdate={Symbol()}

@BrookShuihuaLee Would be great if you could include an explanation like this in your first answer, this way people will understand it better ^^

@CedSharp yeah

@BrookShuihuaLee : That seems like a bad idea. Why would you _want_ to do that?

@BrookShuihuaLee , I agree with @markerikson .
There is a reason why you're supposed to NOT return a clone when the state didn't change. Unless you can provide a valid example in which your solution seems to be necessary, I would strongly recommend against going around how Redux works.

@CedSharp
@markerikson
Because the component AfterConnect will be rerendered frequenctly, by design. It's ok to rerender it once more when its parent component is being rerendered. I don't want to do all computation in the parent component and set tons of props for the AfterConnect.

@BrookShuihuaLee, I'm sorry, but how is your component relevant to the discussion at hand?
If I understand properly, you are talking about your own custom component? ForceUpdate is something very strongly discouraged in the React world and i wouldn't recommend it as a solution.

Instead, you could use an observable solution where you look for changes somewhere else than the state? I don't know why you'd need to re-render that specific component without it's state being modified, but if you know why it should re-render, maybe you could use something like mobx?
(https://github.com/mobxjs/mobx)

I'm trying to understand what your solution is, and why it applies in this situation.

@CedSharp I don't mean that everyone should use forceUpdate. But the recommendation is one thing, the choice is another. React also keep the ReactComponent.prototype.forceUpdate for developers. People need choices.

I was trying to give another choice. Maybe, it's not the best choice.

Not optimal, but I've run into this problem because I've structured my app state incorrectly. I got around the shallow comparison issue by just updating a shallow field on the the slice of the state that was causing me trouble.

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

IMO, not a good solution, but for anyone currently in a bind (i.e. you have to release end of day), it's a quick fix and you don't have to force a render somewhere else.

The code should be refactored so that a shallow comparison, even on a deeply nested state will work.

The state must be immutable. Deep copy the state in reducer, then shallow comparison in connect is enough and effective.

My issue was having an object with arrays within it. As it was shallow comparison it never reached the objects within the child arrays. I solved it by using list:state.obj.list rather than lists:state.obj in mapStateToProps 👍🏼

I just encountered this issue, and after reading the first couple replies, I checked my reducer and noticed I had a typo in there. I renamed a property and updated the INITIAL_STATE but I forgot to change the key that was being updated by this problematic action.

I was basically updating some random key that wasn't used in the component, so obviously, it wouldn't re-render when this random key was updated.

CHECK FOR TYPOS IN YOUR REDUCER! :)

Was this page helpful?
0 / 5 - 0 ratings