React Hooks Revisited: useLayoutEffect
ryanharris.dev
Posted on September 9, 2020
Last time, we learned about the useEffect hook, how it works and when to use it. If you have not read that article yet, I strongly suggest you go back and do so before proceeding any further. Much of what we will discuss below will be about the similarities and differences between useEffect
and useLayoutEffect
, which may not make much sense without having a good grip on the former.
useLayoutEffect and useEffect
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. ~ React docs
While the underlying implementation and execution of these hooks does differ, you may notice that the code snippets below look quite similar. That's because these two hooks have the same function signature!
useEffect(() => {
function getData() {
// Define effect logic here
}
const apiData = getData()
setData(apiData)
}, [])
useLayoutEffect(() => {
function handleResize() {
// Define effect logic here
}
document.addEventListener("resize", handleResize)
return () => {
document.removeEventListener("resize", handleResize)
}
}, [])
To quickly recap, the anatomy of these hooks are comprised of three key pieces:
- The effect
- A dependency array
- A cleanup function
Since we're already pretty familiar with how these hooks are composed, let's look a bit more about what makes them different.
Difference between useLayoutEffect and useEffect
As we saw above, these two hooks are quite similar in terms of their syntax and function signatures, however, the difference between them is quite subtle and has everything to do with timing.
The useEffect
hook fires after render so as not to block the DOM from painting and effecting your application's performance. Due to this behavior, the React documentation advises that when writing new effects you start with useEffect
and only reach for useLayoutEffect
when absolutely necessary.
...we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem... ~ React docs
Unlike useEffect
, useLayoutEffect
fires after all DOM mutations but before the DOM paints. While this is the only difference between these two hooks, it is an important distinction because of when their effects are performed.
But when would you want to use one versus the other? The general rule of hand is as follows:
- Use
useLayoutEffect
when reading, manipulating or observing the DOM - Use
useEffect
for all other effects that don't require interaction with the DOM
If you're still a bit confused, don't worry! It can be hard to wrap your head around not only the difference between these two hooks but their specific use cases, as well. Below, we'll look at a practical example, which will help illustrate the difference a bit more clearly.
In practice
In both of the examples below, we have a simple application that renders a few HTML elements, namely two div
s and a main
, which we can see in App.js
:
return (
<div className="App">
<main className="App__main">
<div className="App__square" />
</main>
</div>
)
Above each <App />
's return, you will see an effect defined with either useEffect
or useLayoutEffect
. The snippet below shows them side-by-side:
useLayoutEffect(() => {
const greenSquare = document.querySelector(".App__square")
greenSquare.style.transform = "translate(-50%, -50%)"
greenSquare.style.left = "50%"
greenSquare.style.top = "50%"
})
useEffect(() => {
const greenSquare = document.querySelector(".App__square")
greenSquare.style.transform = "translate(-50%, -50%)"
greenSquare.style.left = "50%"
greenSquare.style.top = "50%"
})
As I am sure you've noticed by now, the effect function passed to both hooks are exactly the same. Again, the big difference here is the timing of when these effects run.
To start, let's look at the sandbox using useEffect
. You should see the purple circle appear in the top left-hand corner of the screen before it is quickly repositioned and moved to the center of the screen. This happens because useEffect runs after render, so the effect isn't executed till after the DOM paints, which is what causes the unwanted flash of content.
Now, let's look at the useLayoutEffect
hook. If you refresh the page, you should always see the purple circle in the center of the screen and no longer see the circle get quickly repositioned. This is because useLayoutEffect runs before the DOM paints, so the circle has already been positioned correctly before we see the first visual representation of our page. In this example, it would be more appropriate to use useLayoutEffect
because we would not want our users to see what looks like a visual bug.
Next steps
In the examples above, we are using vanilla JavaScript to access and modify the document, which is considered an anti-pattern in React. A more appropriate way to do this would be to use a ref
instead of accessing the DOM directly. Luckily, we'll be covering that in the next article about useRef
!
Posted on September 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 2, 2024