UseEffect vs UseLayoutEffect in React
Initially, when a component first loads and the state changes, it re-renders the DOM
so we can update that state in the browser. The UseEffect hook is a way to run code on every render, which can be useful for many reasons. The UseLayoutEffect hook is a hook you have probably never heard of, and if you have heard of it, you may not know when exactly to use it: this hook and the UseEffect
hook are nearly identical. UseLayoutEffect
differs from useEffect()
because it is not asynchronous.
Goals
This article will identify areas where useLayoutEffect()
would be better than UseEffect()
. We’ll simplify the use of React’s useLayoutEffect()
and explain how to position it to maximize performance and speed up problem-solving. In the course of this tutorial, we will have to build a simple application that resembles the one below, built with the useEffect()
hook:
We will also recreate the above, using the useLayoutEffect()
hook instead:
The differences observed in both apps built with useEffect()
and useLayoutEffect()
respectively will be the core reasons for this article.
Utilizing UseLayoutEffect()
The UseEffect()
and useLayoutEffect()
hooks are mostly identical, and their syntax is the same as seen below;
//useEffect()
useEffect(() => {
setMessage('welcome to open replay blog')
}, [])
//useLayoutEfect
useLayoutEffect(() => {
setMessage('welcome to open replay blog')
}, [])
Key differences between useLayoutEffect() and useEffect()
So, with their identical syntaxes, what is the difference between these two hooks? Well, there are three key differences:
The asynchronous nature of useEffect
The DOM
flashes
The fact that useLayoutEffect
is executed before useEffect
.
UseEffect()
is asynchronous by nature: it will not delay painting the DOM
to the browser, but on the other hand, the useLayoutEffect()
hook is synchronous, and it will delay painting the DOM
to the browser. It runs its code before painting to the browser. So when does this come in handy? Well, it makes the useEffect()
hook the correct and more performant choice about 95% of the time.
However, just that 5% percent of the time useLayoutEffect()
has, it takes full advantage of its potential. When you want to choose useLayoutEffect
over useEffect
and that is because you will want the code to run before it directly modifies the DOM
and that modification, of course, is observable to the user, you don’t want the user to observe that change, and that brings us back to the example that I gave at the beginning of this tutorial.
If you notice, whenever I click the button, the number changes, and the whole section element tends to move, but this is observable to the user. What I do want to do, as seen in the second GIF in the application demo, is have the element move but without displaying the change in number until it has moved.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
A short demo
Here is the code for the demo;
import { useState, useRef, useEffect, useLayoutEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
const [elementStyle, setElementStyle] = useState({});
const elementRef = useRef();
useEffect(() => {
const atRandom = Math.floor(Math.random()*500)-50;
for (let i = 0; i <= 100000000; i++) {
if (i === 100000000) setElementStyle({ paddingTop: `${atRandom}px` })
}
}, [count])
/*the loop above just serves to make the changes in this example visible by slowing them down */
return (
<div className="app">
<div ref={elementRef} style={elementStyle}>
<p>{count}</p>
<div>
<button onClick={() => setCount(prev => prev + 6)}>+</button>
</div>
</div>
</div>
);
}
export default App;
We should be able to have the GIF below after basic styling
.btn{
background-color: rgb(250, 197, 119);
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.app{
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.p{
text-align: center;
text-decoration: none;
font-size: 16px;
}
We need to modify the code with useLayoutEffect()
; this is what it looks like:
useLayoutEffect(() => {
const atRandom = Math.floor(Math.random()*500)-50;
for (let i = 0; i <= 1000000; i++) {
if (i === 1000000) setElementStyle({ paddingTop: `${atRandom}px` })
}
}, [count])
You can see the difference:
DOM
Flashes, another key difference to note here that comes in handy is if you see a flash effect on the screen numerous times, you are changing several things in the DOM
that take a while to process. Therefore, you may see that flash when the repaint happens, like the code below prints:
import React, { useState, useEffect, useLayoutEffect } from 'react';
import './App.css';
function App() {
const [message, setMessage] = useState("hello dev's");
useEffect(() => {
setMessage('welcome to open replay blog')
}, [])
return (
<div className="container">
<h2>{message}</h2>
</div>
);
}
export default App;
Replacing the code below with useLayoutEffect()
returns a different result that solves the flashes. UseLayouEffect()
runs before useEffect()
: this last key difference is minor but still important. Similar to the first difference indicated, useLayouEffect()
runs before useEffect()
.
import React, { useState, useEffect, useLayoutEffect } from 'react'
import './App.css'
function App() {
const [message, setMessage] = useState("hello dev's")
useEffect(() => {
setMessage('welcome to open replay blog')
console.log(message)
}, [])
useLayoutEffect(() => {
setMessage('welcome to open replay blog')
console.log(message)
}, [])
Image:
As we can see above, the useLayouteffect code always comes before useEffect
.
Conclusion
Generally speaking, it is best to always use useEffect
and only switch to useLayoutEffect
if useEffect
genuinely creates flickering in your DOM or produces an inaccurate result. Although useLayoutEffect
is a very helpful hook in some circumstances, useEffect
will work just fine most of the time. Additionally, if useEffect
functions well, it is preferable to use it because it does not prevent painting.