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
Components not re-rendering with connect() #585
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 |
Also, keep in mind that 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 Thanks, all, for the help! I really appreciate the time you took to assist. |
@timdorr |
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. |
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 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. |
The resulting 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
If you return a new object for some slice of state, and Is there something specific that isn't working as you expect it to? I'm not really sure what you mean by your |
It's worth considering whether the additional component should simply be moved down into the |
@ernieturner I know this is old but you might want to update your Troubleshooting Section link. Cheers |
I had this problem, well not exactly.
And it wasn't working. So I applied value directly from props like
And it works just fine :) |
@AlexanderKozhevin Unless I am mistaken, your constructor is missing a |
@cdubois-mh Right you are, thanx for note. |
I've written custom-made app root reducer and get that problem. |
@daedalius That's how a reducer is supposed to work. If you don't return a copy, then react won't always be able to detect that a value changed. 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. 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 |
force update
|
@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 . |
@CedSharp |
@BrookShuihuaLee, I'm sorry, but how is your component relevant to the discussion at hand? 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? 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.
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 |
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! :) |
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 populatedentities
key: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!
The text was updated successfully, but these errors were encountered: