React-Redux: Connecting All Children
It appears to be a fairly common notion that having a single connected container component in your react-redux project is a recommended idea for a variety of reasons. The main one being that it was suggested by the main react and redux teams back in the day. [source 1] [source 2]
They have however admitted since then it having a single top-level connected component was a mistake, although it’s rare to see the store connected to just one top level component now, it’s fairly common to see people recommend not connecting too many components.
So, how many is too many here? 10? 50? 100? 1000? 10000?
I do not know. But I was curious too about the performance cost of connecting too many components. So in one of my projects, I tried connecting upto 11000 components to the store to read their specific resource which was present in an array (looked up by index).
Turns out, that was not a bad idea after all! My application actually worked better because now I was able to pass in props directly to each component from the store that would result in updating only the concerned component, and not every component in the array when I would make a change to one item in the array and then return a new reference to the array.
This is how I would do it:
- Connect parent to store, and read the array of objects into it.
- Map over the array to render the Item-s, and pass in
indexas a prop to it.
- Connect the Item component to the store. In its mapStateToProps, you will have access to the
indexprop passed to the component. Return the object at the
indexyou received, from the Array of objects in your store. So this Item component is now only concerned with that one object.
Why does this work?
Because while the parent will update and loop whenever items updates, it won’t trigger an update for all the contained Item-s. Because the Item component is only getting an index, and that never changes, so React optimizes that away.
And, the Item components will get the updates resources from the store, specified by their index, so only the object that was updated will trigger a render of its respective component.
To see this in action, go to the experiment page and open the browser console to see how many times an update to any Item component has occurred. You’ll see that for the blue (parent) one, each update logs 10 times, once for each Item, while for the coral (children) one, each update logs only once, for the item that was updated. This is of course, after there already have been 10 logs, once for the first render of each item.
The code has a second iteration to it where the function to connect store state to component props is modified a bit. Now instead of returning the original
items array as-is to the component, only to NOT use its contents inside the parent directly anyway, we now return just the length. That prevents even the parent from rendering on subsequent updates. Why does that work?
Shallow comparisons happen at at-least the following points:
- Component props.
Previously, we were already passing in unchanging props to the
Item component, making React not trigger a re-render of every
Item that wasn’t being updated. But the parent component itself was being re-rendered, even though it didn’t quite need to because our changes only concerned one specific
Item component. This was because we were returning a new instance of the
items redux state property.
We changed that to return
itemCount, which is just
items.length, from the
mapStateToProps function passed to the connect HOC. Since
items.length would remain the same in our case, a shallow comparison on them will return
true, hence a re-render would be avoided at this point.
If you’re not completely convinced that this is a good idea, there’s a data-backed follow-up post to back this up.