useEffect & useLayoutEffect
useEffect & useLayoutEffect
React is divided into two types: class components and function components. Since function-based React became the trend, various elements for function components have been added, such as Hooks and Context.
Among these, hooks related to React's lifecycle are an important element in component development. Developers can create various hooks of their own, but React also provides various hooks at the library level so that developers can use React more conveniently and efficiently.
Among them, the hooks widely used for controlling rendering are useEffect, and the somewhat less familiar but similar useLayoutEffect.
In this document, we'll look at the content and usage of the useEffect and useLayoutEffect hooks, and learn how to properly control rendering work.
If you're a React developer, you've certainly used useEffect at least once. The useEffect hook is defined as a hook used in function components to run code that lies outside of React.
Here, "code outside of React" means code such as fetching API data, manipulating a specific DOM element, or using code within the browser's domain.
The biggest characteristic of useEffect is that the code declared inside useEffect is guaranteed to run only after the rendering work has completed. In other words, it always means an operation on the client side.
Client Side
In modern React, the areas where code can run are divided into the server side and the client side.
There is code that can only run in each respective area. For example, on the client side, you can run browser-related code involving cookies, local storage, the DOM, and so on.
Unlike typical web frontend development, in React or similar frontend frameworks, the concept of a component being "rendered" exists, and if logic is run at the wrong stage, problems can occur. For example, if client-side code — such as accessing the DOM or using cookies — is run before the component has been rendered yet, a rendering failure or an equivalent error will occur, and rendering will fail.
Since client-side logic can only be run after rendering has fully completed, useEffect lets you specify code that only runs on the client side.
Below is the basic usage of useEffect.
TSX
useEffect(callback, arr);
You can specify the desired callback method in callback. It cannot receive separate parameters. This callback method will run asynchronously after the rendering work has finished.
arr is the dependency array. Any variable can be assigned in this array, and if an assigned variable changes, useEffect's callback method runs again.
Because of this characteristic, when a specific variable changes, you can intentionally trigger a specific action, allowing you to render appropriately in response to variable changes.
useEffect returns void by default, but you can return a callback method via return. For example, in the following manner.
TSX
useEffect(() => { const handle = () => { console.log(`just resized at ${Date.now()}`) } document.addEventListener('resize', handle); return () => { document.removeEventListener('resize', handle); } }, []);
Here, a callback method is returned via return. This return is code that cleans up the hook, and the conditions under which the return's callback method runs are as follows.
- When the component unmounts.
- When the value in the dependency array changes and the component re-renders.
As shown above, this is used when there is an action that needs to be cleaned up or removed on component unmount or re-render.
Taking the code above as an example, when the component renders, a resize event was added to the global document. If the event is not removed via return, the resize event will accumulate and run multiple times each time the component mounts or re-renders.
TSX
import { useEffect } from 'react'; function Component(): JSX.Element { useEffect(() => { const tag = document.getElementById('target'); if (tag) { tag.innerHTML = `in client side at ${Date.now()}` } }, [ deps ]) return ( <div> <h1>title</h1> <p id="target">initial</p> </div> ) }
The code in useEffect runs after the rendering work has finished. It changes the HTML content of #target, and the user can observe the initial text initial for a brief moment right after rendering, until the code in useEffect runs.
Every time the deps variable changes, the callback method runs again, and each time, you can see the value shown by Date.now() change.
As covered above, useEffect asynchronously runs code after rendering. Since most React developers encounter this frequently, if you're reading this as a React developer yourself, it will likely be familiar enough that no further explanation is needed.
So then, what is useLayoutEffect, and how is it used?
Below is the basic usage of useLayoutEffect.
TSX
useLayoutEffect(callback, arr);
To cut to the conclusion, the usage is exactly identical to useEffect. To the point where hardly any further explanation is needed.
TSX
import { useLayoutEffect } from 'react'; function Component(): JSX.Element { useLayoutEffect(() => { const tag = document.getElementById('target'); if (tag) { tag.innerHTML = `in client side at ${Date.now()}` } }, [ deps ]) return ( <div> <h1>title</h1> <p id="target">initial</p> </div> ) }
As also shown in this example code, simply swapping the useEffect keyword for useLayoutEffect works correctly, and even the final result is identical.
So then, are useEffect and useLayoutEffect simply hidden twins that split apart for some unknowable internal reason, yet behave the same?
As mentioned earlier, useEffect and useLayoutEffect operate in the same way. The difference lies elsewhere — specifically, in the timing of their execution.
The process by which a React page is actually presented to our eyes is broadly divided into two stages. There's the commonly mentioned rendering stage, and the painting stage.
- Rendering: The process of computing the layout and styles to construct the DOM tree
- Painting: The process of actually presenting the DOM tree
While this is generally lumped together and referred to as "rendering," the point at which the page is actually fully presented comes after the painting work has finished. If the amount of script is large or complex, and the computation takes a long time, the user will see a blank white screen for an extended period.
useLayoutEffect is a hook that runs synchronously after rendering, before painting begins. Conversely, useEffect runs asynchronously after painting.
🖼️ Timing of each hook's execution
A simple diagram would look like the one above. Since useLayoutEffect computes before the user sees the page, it is useful for computing initial values.
If you implement this with useEffect, you'll see the default value briefly displayed, and then, after a slight time delay, useEffect's action gets reflected. Since useLayoutEffect runs before painting — before the user can see the page — it can hide the flaws that appear during initial computation.
However, unlike useEffect, it runs synchronously, so if you assign it overly complex computation, the rendering time will lengthen — so be careful.
Let's compare useEffect and useLayoutEffect via CodeSandbox. I wrote two hooks that perform completely identical operations.
Each hook runs a loop 5000 times to display text in the DOM.
Because of useLayoutEffect, the initial rendering takes some time, and right after painting completes and the page is displayed, useEffect runs.
Because of this, the user sees a slight blank space until useEffect's operation finishes.
The usage of useLayoutEffect and useEffect is identical, but a difference arises due to the difference in the timing of their execution.
useLayoutEffect is useful for small, simple initial computations, while useEffect is useful for running various logic such as data fetching and event hooks.
By using the hook appropriate for the purpose, let's effectively manage the rendering of the page.