DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Mike Botsko
Mike Botsko

Posted on

Reducing rerenders in nested/recursive components?

Codesandbox

I'm trying to decide how to port an old pure-js Tree library to React. Because tree components are recursive, no matter my approach I'm seeing rerenders (react devtools profiler w/"record why each component rendered" enabled) because state has changed and needs to be passed to children.

I've been able to avoid rerendering childless nodes by only passing state setters, but since state has to be passed to children, any node with children renders. So if I click a node to select it, all nodes with children rerender.

I'll need to eventually handle more than just an isSelected state, this is just a simple demo, but I can't figure out a good way to do this.

I could write my own logic to determine if a parent node needs to render but it would need know if any child nodes have changed state. A recursive check is bad, so I was thinking of setting some kind of isDirty flag on parent nodes, but being fairly new to react I don't know if there are better ways.

I'm hoping to avoid third party libraries as I'd like for this to be a public library eventually.

Top comments (4)

Collapse
 
lukeshiru profile image
Luke Shiru

It would be best if you simplified it. You have way too much "control." You have to move all states to the parent component (in this case, TreeDemo), and the only place where the state should be updated is there. This way, the entire tree and renders only update based on changes in said state. If, as you said, you'll need more states besides selected, those other states should also live outside the component itself. Design the component stateless, and then for state you can either set it every time on the parent, or you can provide a paired hook with your component to be consumed by others.

I took your CodeSandbox and made an example with a more straightforward approach following the above suggestions so you can take a look here.

Cheers!

Collapse
 
viveleroi profile image
Mike Botsko • Edited on

Thanks, I appreciate your response and effort. I'm looking at your code now.

Can you clarify why it's better to move stuff upward? For example, my demo had the onclick in the TreeNode component but you pass it as a prop from the TreeNodes. Is there any technical advantage to that, or is better for management?

I've been using the react devtools profiler on these trees. I know that render times aren't the only factor, but I notice that when I select a node in my demo, the render takes around 2.2ms but around 5.1ms in yours. That's absolutely trivial but the app I might use this in could have 10k nodes so I'm trying to understand how everything impacts the perf.

EDIT: Reading that article, I do see the mention of "the children are updating the parent's state, so we are effectively going against the one-way data flow." That's a problem in my example.

Collapse
 
lukeshiru profile image
Luke Shiru

React works best when you follow the "one-way data flow" rule, which is state travels down from parent to children, events travel up from children to parent. It has a few advantages, but the top two must be:

  1. React optimizes better when the state is "predictable" (no surprise states coming from children). And said state changes are batched together so sometimes multiple changes can result in a single re-render.
  2. For you and other developers working on that, is way easier to maintain and test (testing a stateless component is trivial, compared to a stateful one).
Thread Thread
 
viveleroi profile image
Mike Botsko • Edited on

I've been updating my local work with your recommended approach, I like it. How would you handle exposing the API methods in useTree to the application using this tree component? If they call useTree they'll just get a new instance. Whatever I do, I need to ensure it works for multiple tree instances. Maybe the ref is still the best approach?

Looking at the flamegraphs (ranked) I see that for some reason the render phase with this approach takes about 10ms but the actual commit happened at 0.8s. Imgur With my original approach, the render time was only ~3ms but the commit happened at 1.1s. Imgur

I realize that perf isn't the single dictating factor in how components are made but I'm curious why your approach takes longer to render yet less time to commit and mine takes less time to render but more time to commit. Again, at these millisecond times it's really not worth getting worked up over but I do need to be aware of perf as we'll have thousands if not more nodes.

EDIT: I tried this approach (expanding parent nodes, not selecting) with 10k nodes (100 nodes with 100 children each). It took 255ms to render and commit at 1.5s. That was a very noticeable lag in the UI. With my original setup, it took 55ms and commit at 0.9s which was better, far less noticeable. I will continue looking at my options, as I'm still learning. One is more "proper" but it's also slower.

12 Gorgeous UI Components for Your Design Inspiration

12 Gorgeous UI components for your design inspiration: cards, text, buttons, checkboxes, icons, loaders and menus.