React useLayoutEffect vs useEffect with examples
alakkadshaw
Posted on August 23, 2023
In this article, we are going to learn about useEffect vs useLayoutEffect.
Introduction
II. useEffects
III. useLayoutEffect
IV. Difference between useLayoutEffect and useEffect
V. Examples: When to use useEffect and useLayoutEffect
VI. Best Practices and When to use useLayoutEffect
and useEffect
VII. Conclusion
- Thank you for reading
React Hooks: An Introduction
React hooks are simple JavaScript functions that enable developers to use state and other react features in functional components
They encapsulate re-useable code in functions, that can be added in components to introduce functionality that was previously available in class components into functional components
Some common hooks include useState
, useEffect
, useContext
and useLayoutEffect
The hooks enable developers to handle side effects, manage local state and read and access context
This article is provided by DeadSimpleChat Chat API for your website and app
By using Hooks developers can create a more readable, concise and maintainable codebase
Side Effects in React: useEffect and useLayoutEffect
Components need to synchronize with external systems, like setting up a server connection, controlling an external library based on react state etc
Here are some of the reasons why managing side effects is important in react
Ability to predict and maintain code: Properly handling effects results in fewer bugs and more reliable code, thus app behaves predictably and it is easier to maintain as well
Ability to Optimize performance: Proper management of effects results in optimization of performance and resource utilization
Cleanup functionality: With proper effects hook you can easily write the code to clean up as well after the effects have taken place
Synchronization: With effects hooks, you can synchronize component lifecycle events with effects.
useEffect
and useLayoutEffect
are two react hooks that provide a way to perform side effects, sync with component lifecycle and clean up after the effect has taken place
useEffect
useEffect is a hook that lets you handle side effects in react.
Components need to connect to the network, call an API or sync with a third-party library or other asynchronous operations.
All of these are not controlled by react and hence are called external systems
useEffect
is used to connect, sync and control external systems. useEffect
runs after the component has rendered on the screen and it can be setup to re-run on every render or when some dependencies change
useLayoutEffect
useLayoutEffect
is similar to useEffect but it runs synchronously after all the DOM but before the browser has rendered anything on the screen
You can use this hook to measure or manipulate the DOM and want to ensure the changes are made before the browser repaints
Understanding useEffect
React useEffect
is a react hook that lets you perform side effects in react. With useEffect you can connect to external systems like connect to the network or server, a third party library.
Syntax and Basic usage
useEffect(() => {
// write logic to handle side effect like callling an API /connecting to a server or connecting to a third party library here
return () => {
// You can create a cleanup function to clean up after the effect has taken place
};
}, [dependencyA, dependencyB]);
useEffect
has the following
A function that contains the logic to handle side effect
An optional dependency array, that has dependencies listed, When the dependencies change, the effect reruns
An optional cleanup function that cleans up after the effect has taken place
Example: Fetching data with useEffect
In this example, we are going to fetch some data using an API and the useEffect hook
import React, { useState, useEffect } from 'react';
function GetDetails() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function getInfo() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await response.json();
setData(data);
setLoading(false);
}
getInfo();
}, []); // If you want the code to run only once when mounted leave the dependency array empty
return (
<div>
{loading ? <p>searching...</p> : <ul>{data.map(item => <li key={item.id}>{item.title}</li>)}</ul>}
</div>
);
}
Clean Up function
As I have already stated the clean-up function is an optional function that can be returned from the useEffect
It is used to perform clean-up after the effect has taken place, this might include unsubscribing from events, or releasing resources.
Here is an example of using a clean-up function to unsubscribe from a WebSocket connection
import React, { useState, useEffect } from 'react';
function SocketConnection() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket('wss://demo.piesocket.com/v3/channel_123?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV¬ify_self');
socket.onmessage = (event) => {
setMessages((prevMessages) => [...prevMessages, event.data]);
};
return () => {
socket.close();
};
}, []); // To run the effect only once during mounting leave the dependency array empty
return (
<ul>
{messages.map((message, index) => (
<li key={index}>{message}</li>
))}
</ul>
);
}
Clean up function
Dependency Array
It is an optional second argument in useEffect. It is an array of dependencies, when these dependencies change it triggers a re-run of the effects
If the empty array is provided the effect will run only once when the component mounts
If the array is omitted then the effects run every time the component is rendered
let us consider an example:
import React, { useState, useEffect } from 'react';
function UserData({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setData(data);
}
fetchData();
}, [userId]); // Effect runs whenever the userId prop changes
useEffects
uselayoutEffect
useLayoutEffect
is a react hook similar to useEffect
with function that has a the side effect logic, a dependency array and an optional callback function can be returned as well
The difference between useLayoutEffect and useEffect is that useLayoutEffect
runs synchronously after all the DOM mutations and before the browser repaints
This hook can be used to measure and manipulate the DOM and when we want to ensure that the changes have been made before the browser repaints. This involves use-cases which might cause flickering or layout jumps
The useLayoutEfffect
should be used when necessary because it can cause performance issues, because of its synchronous nature
Syntax
useLayoutEffect
has a similar syntax as useEffect
useLayoutEffect(() => {
// Some effect logic goes here
return () => {
// optional clean up function
};
}, [dependencyA, dependencyB]);
Example: using useLayoutEffect to synchronise DOM changes
In this example, we are going to update the position of the tooltip based on the position of the target element
import React, { useRef, useLayoutEffect } from 'react';
function ToolTipLocator({ targetRef, children }) {
const tooltipRef = useRef();
useLayoutEffect(() => {
const targetRect = targetRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
tooltipRef.current.style.left = `${targetRect.left + (targetRect.width - tooltipRect.width) / 2}px`;
tooltipRef.current.style.top = `${targetRect.top - tooltipRect.height}px`;
}, []);
return (
<div ref={tooltipRef} className="tipOfTheTool">
{children}
</div>
);
}
DOM changes in useLayoutEffect
In this example we are using the useLayoutEffect
to update the tooltip's position with respect to the target element
Clean Up function in useLayoutEffect
Clean up function is exactly similar to the useEffect, so you can just reference it in from above. Just replace useEffect
with useLayoutEffect
Dependency array in useLayoutEffect
Dependency Array is exactly similar to the useEffect, so you can just reference it from above. Just replace useEffect
with useLayoutEffect
Difference between useLayoutEffect and useEffect
Asynchronous vs synchronous timing of execution
The core difference between useLayoutEffect and useEffect is the timing of the execution of code
while the useEffect is asynchronous the useLayoutEffect is synchronous
useEffect
: The useEffect runs after the component has been rendered and is an asynchronous hook, primarily used to handle external systems like api calls or integration with third-party libraryuseLayoutEffect
: The useLayoutEffect is a synchronous hook, that runs after all the DOM mutations but before the browser layout. It is mainly used for UI and it may cause performance issues as it blocks the browser from painting the UI until the effects have taken place. It is used to avoid issues like flickering or jumps in the UI
Use-Case for useLayoutEffect and useEffect
Here are some of the common use-cases for both of these hooks
useEffect
Connecting to a remote server
Fetching data from an API
Updating the state in response to prop changes
connecting to external third-party libraries
Performing other asynchronous operations
useLayoutEffect
When state changes updating the DOM
Preventing flickering or UI Jumps during DOM manipulations
Sync DOM changes with other components or UI elements
Examples: When to use useLayoutEffect and when to use useEffect
Example 1: Updating the DOM based on state changes
useEffect
In this example we have a component that displays a list of items and a toggle button to show/ hide the visibility of the list
We can use useEffect to update the visibility of the list based on the state changes
import React, { useState, useEffect } from 'react';
function TogglableList() {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const listElement = document.getElementById('user-list');
if (isVisible) {
listElement.style.display = 'block';
} else {
listElement.style.display = 'none';
}
}, [isVisible]);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>Show / Hide list</button>
<ul id="user-list">
{/* List of items goes here */}
</ul>
</div>
);
}
Here we are using the useEffect
to update the show/hide property of the list using the isVisible
state
Here DOM update is not critical to the calculations of layout or to asynchronous operation with useEffect
hook
useLayoutEffect
Now, let us consider an example where visibility changes require a synchronous operation and we need to use useLayoutEffect
Consider a component that measures the height of a panel that can collapse and we are animating the height based on the isExpanded
state
import React, { useState, useLayoutEffect } from 'react';
function PanelThatCanCollapse({ children }) {
const [isExpanded, setIsExpanded] = useState(false);
useLayoutEffect(() => {
const panelElement = document.getElementById('collapsible-panel');
if (isExpanded) {
const heightOfThePanel = panelElement.scrollHeight;
panelElement.style.height = `${heightOfThePanel}px`;
} else {
panelElement.style.height = '0';
}
}, [isExpanded]);
return (
<div>
<button onClick={() => setIsExpanded(!isExpanded)}>Show / Hide</button>
<div id="collapsible-panel" className="collapsible-panel">
{children}
</div>
</div>
);
}
height adjustment using useLayoutState
Here we are using the useLayoutEffect because the useLayoutEffect
synchronously updates the height of the panel before the browser paints
We need the height update to be made synchronously because otherwise it would break the UI for a little time and that would be a bad experience for the user.
In short, where the UI would break we need synchronous effect resolution thus we use useLayoutEffect
other wise we use useEffect
Example 2: Updating the DOM and Measuring the DOM
We are considering an example where we will measure the DOM and update the DOM based on the measurements
useEffect
We have a component which displays a notification, we need to center it horizontally in the screen.
We can use useEffect
to measure the length of the notification bar and update its position to center it on the screen
import React, { useState, useEffect } from 'react';
function Toast({ toastMessage }) {
const [leftPosition, setLeftPosition] = useState(0);
useEffect(() => {
const toastElement = document.getElementById('toast');
const toastWidth = toastElement.offsetWidth;
const screenWidth = window.innerWidth;
setLeftPosition((screenWidth - notificationWidth) / 2);
}, [message]);
return (
<div id="toast" className="toast" style={{ left: leftPosition }}>
{toastMessage}
</div>
);
}
center the toast in the center of the screen
In this example we are using useEffect
to measure the width of the toast notification based on the width of the screen.
Since measurement and updating the position are not critical to rendering the UI on the page we can use useEffect
to manage this.
useLayoutEffect
In this example we have a tolltip next to an input field, and we want to ensure that the tooltip is propperly positioned before the browser repaints
import React, { useRef, useLayoutEffect } from 'react';
function InputTooltip({ textOfTooltip }) {
const inputRef = useRef();
const tooltipRef = useRef();
useLayoutEffect(() => {
const inputRect = inputRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
tooltipRef.current.style.left = `${inputRect.right + 20}px`;
tooltipRef.current.style.top = `${inputRect.top + (inputRect.height - tooltipRect.height) / 2}px`;
}, [TextOfTooltip]);
return (
<div>
<input ref={inputRef} type="text" />
<div ref={tooltipRef} className="tooltip">
{textOfToolTip}
</div>
</div>
);
In this example we are using useLayoutEffect
to synchronously position the tooltip next to the input field
We are using useLayoutEffect
because we need the tooltip exactly next to the input field when the page renders on the screen, if the tooltip comes at a later point in time it might cause issues like UI jumps.
Example 3: Fetching user data with useEffect
We are building a component that fetches the user data from an API when the component mounts
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(`https://jsonplaceholder.typicode.com/users`);
const data = await response.json();
setUserData(data);
setLoading(false);
}
fetchData();
}, [userId]);
return (
<div>
{Searching ? (
<p>Searching...</p>
) : (
<div>
<h1>{userData.name}</h1>
<p>Email: {userData.email}</p>
{/* list of user details here */}
</div>
)}
</div>
);
}
Here we are using the useEffect
to fetch the user data from the JSON placeholder website and render it when the component mounts.
We have also added userId as a dependency, so when ever the userId gets upadated the effects run again thus calling the API again to update the user details
we are also displaying the user data or a seaching indicator based on the state of the component at that point in time
Example 4: Sync the height of 2 elements with the help of useLayoutEffect
We have 2 elements on a page and we want to make their heights in sync with each other such that they are always equal
import React, { useRef, useLayoutEffect } from 'react';
function InSyncHeight({ leftColumn, rightColumn }) {
const leftRef = useRef();
const rightRef = useRef();
useLayoutEffect(() => {
const leftHeight = leftRef.current.offsetHeight;
const rightHeight = rightRef.current.offsetHeight;
if (leftHeight > rightHeight) {
rightRef.current.style.height = `${leftHeight}px`;
} else {
leftRef.current.style.height = `${rightHeight}px`;
}
}, [leftColumn, rightColumn]);
return (
<div className="InSyncHeight">
<div ref={leftRef} className="column-left">
{leftColumn}
</div>
<div ref={rightRef} className="column-right">
{rightColumn}
</div>
</div>
);
}
Here we are using useLayoutEffect
to measure the height of both the colums and then set the height of the smaller column to match the height of the taller column
we have also added both the column variables in the dependency array such that whenver the height of any of these colums changes then the useLayoutEffect will run again
Best Practices and When to use useLayoutEffect
and useEffect
When to choose useLayoutEffect and useEffect
useEffect
runs asynchronous and does not cause any performance issues and thus should be the default choice whenever you want to deal with external systems such as
calling an API
updating DOM based on state changes
connecting with a remote server
connecting with third party library
Only for things that might cause an UI issue such as flickerring or UI jumps if the operation or connection with the external system is performed asynchronously then useLayoutEffect
should be used
use the useLayoutEffect
in the following scenarios
When measuring or manipulating the DOM you need to make sure that Changes are made before the browser repaints
Effects must be in sync with the component rendering to avoid momentary breaks in UI rendering.
Tips for optimizing performance
use the dependency array to control when the effects runs
Avoid performance resource intensive tasks in
useLayoutEffect
as this would slow down the UI rendering, whenever the effect re runsProperly run the clean up function when the effects have run. As not performing proper clean-up might result in bugs or memory leaks
Try to encapsulate complex logic in custom hooks
Common mistakes and how to avoid them
- Not managing the dependency Array: Remember these points with regard to the dependency array
Always state the reactive values in the dependency array, when reactive values change the effect will re-run
Leaving the dependency array empty will result in the effect running only once when the component mounts
Not providing the dependency array results in the effects running every time the component reruns
2. Overusing useLayoutEffect
Only use the useLayoutEffect
where it is necessary, defaulting to useEffect
whenever possible. As useLayoutEffect
is synchronous it may cause performance issues and it is render blocking as well. So, to avoid performance issues always default to useEffect
3. Always list reactive values in the dependency array
Whenever you are using component values that are reactive, always list them in the dependency array. This ensures that whenever these values change the effect will run
- Not running the clean-up function properly or not running the clean-up function at all
Clean up function is an important part of the effects, after the effects have taken place always free up the resources and clean up. Not doing the cleanup properly may cause bugs or memory leaks
Conclusion
I hope you liked the article. Thank you for reading.
This article is brought to you by DeadSimpleChat, Chat API for your website and app
Posted on August 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 8, 2024