Today, we're going to explore a fascinating aspect of React development that often goes unnoticed: how the useEffect hook affects performance. As React developers, we frequently use this handy tool in our projects.
But have you ever wondered why it runs after the component has been rendered on the screen?
I've always been curious about this. Why does it work that way?
In this post, I'll walk you through the reason behind this choice made by the React team and see how it can actually make your application faster. We'll use practical examples to demonstrate the concept.
Are you ready? Then hit that ❤️ button on the right, and Let's get started!
Let's talk about localStorage, also known as window.localStorage
. It's a handy browser feature that allows us to store data using keys and values. However, there's a slight downside to it.
You see, localStorage works synchronously, which means that it may cause some issues.
Yes, let me explain!
console.log("A"); // A
localStorage.setItem("key", "value"); // An assumed ongoing process...
console.log("B"); // I am still waiting.....
In the code snippet above, something interesting happens. First, we see the console logging of the string A
. Then, we have localStorage.setItem()
doing its thing, which takes about 200 milliseconds to finish.
During this time, the console.log("B")
patiently waits for localStorage.setItem()
to complete before it can log anything.
Now, why is this not ideal?
Well, it can potentially cause your application to slow down right away.
Nobody wants that, right?
If you only need to store a few items, it's not a big deal to use localStorage. However, it's generally not recommended to rely on it too heavily because it tends to be quite slow in terms of performance.
So, keep this in mind when working with localStorage, and try to use it sparingly, especially if you want to keep your application running smoothly.
If you haven't had the chance to use useEffect, I highly recommend reading this detailed guide about useEffect from @nirmalkumar before you proceed.
Let's imagine you want to use the localStorage
feature in a React component. In that case, it's recommended to use it within a useEffect
hook for a couple of reasons:
It's considered a side effect because it modifies something outside the React component.
It can also improve performance, as I'll explain.
To give you an example of why performance could be affected, let's assume we have a large array of objects stored in a data.js
file. We can import this array and save it into localStorage
within a React component.
Here's the code snippet:
import {useState} from "react";
import data from "./data.js";
function App() {
const [random, setRandom] = useState(Math.random());
// THIS IS BAD FOR PERFORMANCE
localStorage.setItem("data", JSON.stringify(data));
return <>
<h1>{random}</h1>
<button>Re-render</button>
</>
}
The code snippet above could potentially run into a small issue. If the localStorage.setItem()
function takes around 200ms to complete, it can cause a noticeable delay in showing updates on the user's screen.
This delay might not be ideal for the performance of your application.
To illustrate this issue, I have taken the example code and exaggerated it by including 200 localStorage.setItem()
and localStorage.getItem()
calls. You'll see the impact of this in just a moment.
To address this performance concern, I have come up with an optimized version of the code. In this optimized version, I encapsulated the 200 localStorage calls within a useEffect
hook.
This is why the code execution will be more efficient, and the user will experience smoother updates on their screen.
Here’s the code:
import {useState, useEffect} from "react";
import data from "./data.js";
function App() {
const [random, setRandom] = useState(Math.random());
// this is BETTER for performance
useEffect(() => {
localStorage.setItem("data", JSON.stringify(data));
});
return <>
<h1>{random}</h1>
<button>Re-render</button>
</>
}
If we wrap the localStorage
calls with useEffect
, it allows the component to render on the screen first, and then perform the localStorage
operations afterward. This approach helps prevent any performance issues that may arise when dealing with a large number of components.
To demonstrate this, let's take a look at an exaggerated example where I increase the number of localStorage
calls to simulate potential performance challenges.
Click on the button labeled Re-render as fast as you can in the below example.
In this case, we directly make 200 localStorage calls inside the component itself.
Pay attention to the responsiveness of the button.
Now, do the same in this example. This time, we use the useEffect
hook in React to make the 200 localStorage calls.
Notice any differences in the responsiveness compared to the first example 😃
If you take a close look at the first example, you'll notice that the button stays active (highlighted in red) because it's getting stuck while processing all the JavaScript code.
Just remember, useEffect
isn't a cure-all solution. If the code inside useEffect
is really slow, it can still affect the overall performance.
Have you ever faced any performance challenges while working on your React projects?
How did you tackle them?
I'd love to hear your thoughts and experiences, so feel free to share them in the comments section. And if you found this post helpful, don't forget to hit the ❤️ button on the top right and spread the word among your fellow React developers.
Keep exploring, and enjoy coding 👋