Introduction
useEffect and useLayoutEffect are two React hooks used to manage side effects in a React component. When building React applications, understanding when to use useEffect versus useLayoutEffect can significantly impact your app’s performance and user experience. Both hooks serve to handle side effects in functional components, but they behave differently during the rendering process. Let’s dive deeper into how they work, their differences, and some practical examples.Prerequisites
- Node.js and npm
- React
- Typescript
- React Hooks
What is useEffect?
useEffect is a commonly used hook that allows you to perform side effects in functional components. Side effects could be anything that affects things outside of your component, such as data fetching, DOM manipulation, or logging. useEffect is designed to run after the render is committed to the screen. The useEffect hook in React takes two arguments:- Callback function : This function contains the side-effect code that you want to execute.
- Optional dependency array : The callback function is called only when any of the values in this array change. If no dependencies are specified, the callback runs after every render.
When to use
In most cases, useEffect should be your go-to hook for handling side effects. Here are some scenarios where useEffect is more appropriate:- Data fetching : Trigger an API call after the component mounts.
- Adding event listeners : For events like scroll or resize.
- Updating non-UI state : Updating states that don’t impact the immediate layout.
useEffect(() => {
// Side effect function
}, [dependencies]);
Here are some examples of how to use useEffect:
- useEffect with an Empty Dependency Array : This runs only once when the component mounts, like componentDidMount.
import React, { useEffect } from 'react';
function UseEffectWithEmptyArray() {
useEffect(() => {
console.log('Component mounted!');
}, []); // Empty array ensures it only runs once
return Check the console to see the mount message.;
}
- useEffect with Specific Dependencies : This will run whenever the specified dependencies change.
import React, { useState, useEffect } from 'react';
function UseEffectWithDependencies() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count changed: ${count}`);
}, [count]); // Runs only when `count` changes
return (
Count: {count}
);
}
- Cleaning up in useEffect : If the effect returns a cleanup function, it will run when the component unmounts or before the next effect runs.
import React, { useState, useEffect } from 'react';
function TimerComponent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
return () => {
clearInterval(interval); // Cleanup to avoid memory leaks
};
}, []); // Empty dependency array, runs only on mount and unmount
return Timer: {seconds} seconds;
}
What is useLayoutEffect?
useLayoutEffect is similar to useEffect, but it runs synchronously after all DOM mutations and before the browser paints. This hook is crucial when you need to perform DOM measurements or manipulate the layout synchronously because it guarantees that the effect will run before the user sees any visual changes.
The useLayoutEffect hook in React takes two arguments:
- Effect function : This function contains the effect code that you want to run after the DOM has been updated but before the browser has been painted.
- Optional dependency array : The effect function is only called when any of the values in this array change. If no dependencies are specified, the effect will run after every render.
This hook is similar to useEffect, but it fires synchronously after all DOM mutations. It can be useful for effects that need to interact with the layout before the screen is updated.
When to use
Use useLayoutEffect when you need to make changes that affect the layout or DOM measurements before the user sees them. Some common scenarios include::
- Measuring elements : Getting the dimensions of an element and setting the layout accordingly.
- Synchronous DOM manipulation : Ensuring that any changes are applied before the user sees them.
Syntax
useLayoutEffect(() => {
// runs synchronously after commit
return () => {
// cleanup
}}, [input])
Comparison useEffect Vs useLayoutEffect
Features | useEffect | useLayoutEffect |
Execution Timing | Runs asynchronously after the render is painted to the screen. | Runs synchronously after the DOM is updated but before the browser paints. |
Use Cases | Non-blocking side effects like data fetching, adding event listeners, or updating non-UI state. | DOM measurements, layout adjustments, or any visual updates are required before the user sees the component. |
Performance Impact | Doesn’t block rendering; allows the UI to load faster. | Can block the paint until it completes, which might slow down the initial render if overused. |
Ideal for | Side effects that do not affect layout or visual presentation immediately. | Effects that impact layout, positioning, or require accurate DOM measurements before the component is shown. |
Common Examples | API requests, setting up event listeners (e.g., scroll, resize), logging. | Measuring dimensions of elements, synchronizing animations, or making visual adjustments before paint. |
Frontend Setup in React
We’ll write the code to get a better understanding of useEffect and useEffectLayou hooks.
Step-by-step Implementation
- Create a React App
Let’s create a React app using the following command:
/Create a react app using the following command
npx create-react-app react-hooks-app --template typescript
After the setup is complete, navigate to your project folder:
// Navigate to the project folder
cd react-hooks-app
npm start
This will launch the development server, and you’ll be able to view the app at http://localhost:3000.
- Project Structure
Make sure your project has the following folder structure:
- useEffect() Hooks Example
We’ll create a standalone UseEffectExample component within the components folder. This component will include a search feature that dynamically filters a list of users based on the search input.
Using the useEffect hook, we’ll monitor changes in the search query to update the filtered user list in real-time. This approach demonstrates how useEffect can be effectively used to handle initial data fetching and to perform live filtering as the user types.
// UseEffectExample.tsx
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
}
const UseEffectExample: React.FC = () => {
const [users, setUsers] = useState([]);
const [filteredUsers, setFilteredUsers] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
// Fetch users on component mount
useEffect(() => {
const fetchUsers = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data: User[] = await response.json();
setUsers(data);
setFilteredUsers(data); // Initially show all users
};
fetchUsers();
}, []); // Empty dependency array to fetch data only on mount
// Update filteredUsers when searchQuery changes
useEffect(() => {
const filtered = users.filter(user =>
user.name.toLowerCase().includes(searchQuery.toLowerCase())
);
setFilteredUsers(filtered);
}, [searchQuery, users]); // Runs whenever searchQuery or users changes
return (
useEffect Example
setSearchQuery(e.target.value)}
/>
{filteredUsers.map((user) => (
- {user.name}
))}
);
};
export default UseEffectExample;
- Fetching Data : When the component mounts, useEffect fetches the list of users from an API and sets it in the users state.
- Filtering Users : Another useEffect monitors searchQuery and users. Whenever the searchQuery changes, it filters the users to include only those whose names contain the search text.
- Updating the Filtered List : The filtered results are stored in filteredUsers, which is then rendered in the component
- useLayoutEffect() Hooks Example
We’ll create a standalone LayoutEffectExample component within the components folder.
// LayoutEffectExample.tsx
import React, { useLayoutEffect, useRef, useState } from "react";
const LayoutEffectExample: React.FC = () => {
const [width, setWidth] = useState(0);
const divRef = useRef(null);
useLayoutEffect(() => {
// Ensure divRef is defined before accessing clientWidth
if (divRef.current) {
const handleResize = () => {
setWidth(divRef.current!.clientWidth);
};
// Set initial width
handleResize();
// Add resize listener
window.addEventListener("resize", handleResize);
// Cleanup event listener on unmount
return () => window.removeEventListener("resize", handleResize);
}
}, []); // Empty dependency array to run only on mount/unmount
return (
useLayoutEffectEffect Example
Resize the window to see changes
Width of the div: {width}px
);
};
export default LayoutEffectExample;
-
- Ref for the element : divRef is a reference to the <div> element.
- useLayoutEffect : Runs after DOM updates, allowing us to measure the element’s width before the browser repaints.
- Event Listener : The resize event updates the width when the window resizes, and the cleanup removes the listener on component unmount.
- Integrating the Components into the App : Now, we’ll import UseEffectExample and LayoutEffectExample components into the main App.tsx file. This component will render the UI.
//App.tsx
import React from "react";
import "./App.css";
import UseEffectExample from "./components/UseEffectExample";
import LayoutEffectExample from "./components/LayoutEffectExample";
function App() {
return (
<>
>
);
}
export default App;
Conclusion:
Understanding when to use useEffect versus useLayoutEffect in React enhances performance and user experience. Use useEffect for non-blocking tasks like data fetching and event listeners, as it runs after rendering, ensuring faster load times. Use useLayoutEffect for layout-related tasks requiring synchronous execution after DOM updates but before painting, such as DOM measurements and layout adjustments.
Thoughtful use of these hooks improves performance, prevents layout shifts, and ensures a smoother user experience.