The surprising reason react is slow (sometimes)
It is widely accepted that if you want to build a flexible, scalable and modern website, react is the way to go. but what happens when you have a problem that the beloved library cannot help you with?
React offers a very strong and maintainable way to deal with dom changes. The top down flow of the change detection allows each interaction to update a parent component which trickles down information to a different target, causing a re-render based on the updated data. The issue starts when the somewhat convoluted flow needs to happen multiple — possibly hundreds — of times.
For example, while working in Just Eat Takeaway’s TLV innovation center I was assigned with creating an advanced and reusable bottom drawer for one of our B2B products but I kept hitting a brick wall- The animation was just too janky and behaved unpredictably!
Let’s take a look at a simplified example of the issue I was facing. A simple square that needs to make it’s way across the screen diagonally as you scroll down the content of a div.
Keep in mind — this behavior cannot be implemented with a pure css solution since the position of the cube needs to be updated according to the element.scrollTop
in real-time
Go ahead, grab the scrollbar and scroll up and down as fast as you can!
Huh? the square seems to be moving in circles as you scroll up and down. Is the browser too slow for this sort of animations? Is there a bug?
Not exactly. Look at the console, this is not a logic issue but a performance one! As you scroll up and down the page, the react renderer tries to keep up, causing hundreds of re-renders which are an expensive operation even in a lightweight framework such as react.
I bet there is a way to do it with react, they wouldn’t just let us down like that!
Not really. React is working as Intended. You are keeping track of the position of the scrollbar in a parent component which causes everything below it in the virtual DOM to re-render 😢.
You may be tempted to think of a workaround using a useEffect
& useRef
combo example to bypass the useState
’s re-render, but the problem is that you are trying to fight one of react’s core mechanisms:
By bypassing the change detection — you are preventing the change itself!
Ok, Maybe I’ll just go and convince my Product Manager that a cube was a silly idea all along…
You’re not getting out of it this easily. Now that we understand that the issue is the usage of react’s change detection, Let’s think of another way we can update the dom…
Vanilla JavaScript of course!
Think about it, you can just modify the element directly to handle the DOM change.
Grab the scrollbar and scroll up and down as fast as you can!
Now the animation is much smoother and as you can see in the logs, the component re-renders exactly once (well, twice in react 18’s strict mode).
Interestingly, while Angular is commonly considered inferior to react in terms of performance due to using the regular DOM instead of a virtual one, this flaw becomes an advantage when it is required to update the DOM as fast as possible.
This paragraph may or may not have been included due to a threat on my life by Eliran Eliassy, A Beyoncé-level Angular celebrity.
Let’s look at an equivalent functionality in angular:
As usual — Grab the scrollbar and scroll up and down as fast as you can!
So that’s it? The bad guys win? Did angular really defeat react at something?
Not so fast. react-spring to the rescue!
react-spring allows us to work outside the react’s change detection flow in a declarative way that really reminds of react’s state pattern.
ets look at one last example and then you can continue looking at memes at work:
One last time — Grab the scrollbar and scroll up and down as fast as you can!
Important note- a few readers pointed out that react-spring did jump a bit. Could possibly be caused by an old machine.
Seriously? a whole package just for that?
That’s up to you. react-spring is a wonderful library with solid documentation and a declarative syntax. In my opinion those reasons alone justify not using a DIY solution. Also this is what usehooks.com — a website dedicated entirely to providing you with hook code snippets instead of including dependencies — has to say about react-spring:
I try to avoid including dependencies in these recipes, but once in awhile I’m going to make an exception for hooks that expose the functionality of really useful libraries. — https://usehooks.com/useSpring/
So, If it’s good enough for usehooks, it’s good enough for me.
To summarize:
When you have a hammer, every problem looks like a nail. Don’t be tempted to solve every issue with a useState
as it may cause some issues
Hope you learned something new!
TL;DR
use react-spring