React-Redux: Connecting all children (part 2)

React-Redux: Connecting all children (part 2)

Here we’ll look into component updates, DOM updates, and a basic benchmark comparison. If you didn’t see the last post, go here. It talks about two different approaches of connecting a component to store in basic terms.

Feature image courtesy: Image Link | Article


Component Updates

We had previously seen from logs, that the children connect approach resulted in the component being “rendered” far less often than the parent-only approach. Just to see more details on this, I put the application through the React Profiler, and the results were in line with what we saw earlier. Each item in the parent-only list was rendered again, while only the intended item in the parent-child list was updated.

Flame graph showing the time spent on rendering each component in both lists.

It’s a little blurry, but you can tell what is going on. The coloured bits represent renders. In case you wonder why the second list takes up so much width despite being all gray, you might want to see how to read this flame graph here.


DOM updates

From our previous experiment, we know that connecting each child to the store while passing a very basic and unchanging props from the parent results in fewer overall renders without any lifecycle events of memo related optimizations. But, if you thought that React’s internal diffing logic should optimize away the changes we were seeing, you’d be right. React indeed does optimize away the number of changes to the DOM that would be made.

We can verify that using the good old MutationObserver API.
Here’s the snippet that you can paste in the browser console on the experiments page.

const config = {
  attributes: true,
  childList: true,
  subtree: true,
  characterData: true
};

const observer = new MutationObserver(
  mutList => console.log(mutList)
);

observer.observe(
  document.querySelector('[class^="styles-module--two-items"]'), 
  config
);
The result of running the snippet on the experiments page.
The result of running the snippet on the experiments page.

What we’re trying to do is, listen for any changes to the DOM on the element containing both lists, and log them out.

You can see that we’ve made a total of four changes, as evident by the text “2 updates” visible in two of the list items. And you can see that we have four logs with MutationRecord present twice in arrays, with each array entry belonging to each list in the page.

This tells us that despite the different approaches to connecting the store to components, as far as our simplistic experiment is considered, the number of DOM updates remained the same in both cases.


Benchmarking

An array of 10 items is not cool. You know what’s cool?
An array of 5000 items.

We’ll change our state to have 5000 items instead of 10, and we’ll run the same above profilers to see how it fares. Will we see very similar render times? Will there be a considerable difference? Let’s see.

You can test this out yourself by changing this line to this:
items: Array(5000)

Parent-Only

Each render took close to 2 seconds, and the list has 5000+ items.
Each render took close to 2 seconds, and the list has 5000+ items.

Parent-Child

Each render took around 5ms or less, and you're seeing the entire list in the screenshot.
Each render took around 5ms or less, and you’re seeing the entire list in the screenshot.

The difference is STAGGERING. Taking the render times in the first case at 1600ms, and the second case at 5ms, that’s an improvement of 320x!

Do note, that this is with 5000 children connected to the store, and we’re using basic render-prevention measures in both cases, like using useCallback or useMemo as application to prevent creating new instances of functions or values.


Extra

Another reason in favor of connecting all children in such a use-case to the store is that even if React prevents the DOM from updating, and even if you have few elements so performance isn’t as big a bottleneck, you may still have side-effects in your component. If you have a useEffect hook in your component that takes no dependencies, it will fire on every render, and that means a ton of unnecessary function calls that could have been avoided.

Sure, you could wrap a function component in React.memo, but that would be you trying to fix a problem that could have been avoided in the first place with some clever store-connecting.



Leave a Reply

Your email address will not be published. Required fields are marked *