useLayoutEffect - when to use it?
I recently attended an interview for React JS and the one question that I answered without actually ever implementing it was about the useLayoutEffect hook. I have heard about it from my colleague who had mentioned using it for some animation. I just looked for its definition then and it never occurred to me to implement it or to play around it until the day of the interview, where I could only answer what I read and couldn't really code to put out an example of it. So, after spending a good two hours on it, I am sharing the code snippets and explaining them here for an insight into the useLayoutEffect hook and how it is different from the useEffect hook, with examples.
useLayoutEffect
is a React Hook that is similar to the useEffect
Hook, but it runs synchronously immediately after all DOM mutations before the browser has a chance to paint those changes on the screen. This is useful for performing actions that depend on the layout or dimensions of the DOM elements, such as animating transitions or calculating measurements.
The signature useLayoutEffect
is the same as useEffect
: it takes two arguments, a function and an optional array of dependencies. The function argument will be executed after the initial render and after every update to the component, while the dependency array determines whether or not the effect should be re-run based on changes to its dependencies.
Here is an example of using useLayoutEffect
to perform a simple animation:
import { useState, useLayoutEffect } from 'react';
function App() {
const [show, setShow] = useState(false);
useLayoutEffect(() => {
const el = document.getElementById('box');
el.style.opacity = '1';
el.style.transform = 'translateX(0)';
}, [show]);
return (
<div>
<button onClick={() => setShow(!show)}>Toggle Box</button>
{show && <div id="box" style={{ opacity: 0, transform: 'translateX(-50px)' }}>This is a box.</div>}
</div>
);
}
In this example, we are using useLayoutEffect
to update the styles of the #box
element when the show
state changes. The useLayoutEffect
hook runs synchronously after every update, so the DOM changes will be immediately reflected in the browser, allowing for smooth animations without flickering or delays.
It is important to note that because useLayoutEffect
runs synchronously, it can potentially cause performance issues or jank if the effect takes too long to execute. In these cases, it may be better to use the useEffect
Hook instead, which runs asynchronously after the browser has painted the changes on the screen. However, for certain use cases such as animations or layout-dependent effects, useLayoutEffect
can be a useful tool for achieving a smooth and responsive user experience.
- Updating CSS Styles
import { useState, useEffect, useLayoutEffect } from 'react';
function App() {
const [color, setColor] = useState('red');
useEffect(() => {
const el = document.getElementById('box');
el.style.backgroundColor = color;
}, [color]);
useLayoutEffect(() => {
const el = document.getElementById('box');
el.style.borderColor = color;
}, [color]);
return (
<div id="box" style={{ width: '100px', height: '100px', border: '5px solid black' }}>
This is a box.
</div>
);
}
In this example, we are using useEffect
to update the background colour of the #box
element whenever the colour
state changes, and we are using useLayoutEffect
to update the border colour of the same element. Because useLayoutEffect
runs synchronously before the browser has a chance to paint the changes, the border colour will be updated before the background colour, resulting in a brief flicker where the border colour is visible before the background colour is updated.
- Measuring Element Dimensions
import { useState, useEffect, useLayoutEffect } from 'react';
function App() {
const [width, setWidth] = useState(null);
useEffect(() => {
const el = document.getElementById('box');
setWidth(el.clientWidth);
}, []);
useLayoutEffect(() => {
const el = document.getElementById('box');
setWidth(el.clientWidth);
}, []);
return (
<div id="box" style={{ width: '50%' }}>
This is a box. Its width is {width}px.
</div>
);
}
In this example, we are using both useEffect
and useLayoutEffect
to measure the width of the #box
element and update the width
state accordingly. Because useEffect
runs asynchronously after the browser has painted the changes, it may not always reflect the most up-to-date measurements of the element. In contrast, useLayoutEffect
runs synchronously before the browser paints the changes, ensuring that the width
state is always up-to-date with the latest measurements.
Overall, the choice between useEffect
and useLayoutEffect
depends on the specific use case and the desired behavior of the effect. If the effect relies on the layout or dimensions of DOM elements or needs to perform synchronous updates to the DOM, useLayoutEffect
may be more appropriate. Otherwise, useEffect
is generally a safer and more performant choice.