react
The Definitive Guide to useEffect
author
Luis Paredes
published
Jun 9, 2023
When working with React, it's essential to understand how to manage side effects within your components. This is where the useEffect
hook comes into play.
What are side effects?
In frontend applications, common side effects refer to operations or interactions that occur outside the scope of the application's core logic and can affect the application's state, user interface, or the environment it operates in.
These side effects typically involve tasks such as making network requests, manipulating the DOM, subscribing to events, accessing browser APIs, or interacting with external services. Side effects are necessary to handle asynchronous operations, integrate with external systems, and ensure proper interaction with the user interface and the browser environment.
Managing these side effects effectively is crucial for maintaining a robust and responsive frontend application.
What is useEffect
and when do you need to use it?
useEffect
is a built-in hook in React that allows you to perform side effects in functional components. It enables you to handle tasks like data fetching, subscribing to events, or modifying the DOM.
In the next sections, we'll see the typical useEffect
patterns as well as examples of how to use it for the most common use cases.
useEffect
patterns
The useEffect
function takes two parameters: a callback function and an optional array of dependencies. The callback function represents the side effect you want to perform, and when and how often it will be called will depend on whether or not you pass the second parameter and the contents of that parameter when you pass it.
Using useEffect
without dependencies
If you intend to call the side effect callback after every render of the component, you can invoke the hook using only the first argument:
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Side effect code goes here
// This code will run after every render
});
return (
// Component JSX
);
}
Using useEffect
with an empty dependency array
In some cases, you may want to execute the side effect callback only once when the component mounts. To achieve this, you can provide an empty dependency array as the second argument to useEffect
:
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Side effect code goes here
// This code will run only once when the component mounts
}, []);
return (
// Component JSX
);
}
By passing an empty array as the dependency list, you indicate that the effect has no dependencies and should only execute once during the component's initial render. This is particularly useful when performing tasks like:
- initializing external libraries,
- setting up event listeners, or
- subscribing to data sources.
It's important to note that the effect will not be triggered again even if the component re-renders, making it an ideal choice for scenarios where you want to perform setup or initialization operations that should not be repeated unnecessarily.
Using useEffect
with dependencies
By providing an array of dependencies as the second parameter to useEffect
, you can control when the effect should run. The effect will only execute if one or more of the dependencies have changed since the last render.
import React, { useEffect, useState } from 'react';
function MyComponent() {
const [data, setData] = useState([]);
useEffect(() => {
// Side effect code goes here
// This code will run when `data` changes
}, [data]);
return (
// Component JSX
);
}
Using useEffect
with a cleanup function
In some cases, your side effect might need to perform cleanup before the component unmounts or before the effect is run again. Returning a cleanup function is useful for tasks like:
- canceling subscriptions,
- removing event listeners, or
- clearing timers.
To clean up an effect, you can return a cleanup function from the effect's callback function. The cleanup function will be executed before the component is removed from the DOM or before the effect is run again.
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Side effect code goes here
return () => {
// Cleanup code goes here
// This code will run before the component unmounts or before the effect is run again
};
});
return (
// Component JSX
);
}
Common use cases
useEffect
is a versatile hook that can be used in various scenarios. In this section, we'll explore some common use cases and demonstrate how useEffect
can be applied effectively.
Fetching data with useEffect
One common use case is fetching data from an API. With useEffect
, you can trigger the data fetching operation when the component renders for the first time or when certain dependencies change.
import React, { useEffect, useState } from 'react';
function DataComponent() {
const [data, setData] = useState([]);
useEffect(() => {
// Fetch data from API
fetchData().then((response) => setData(response));
}, []);
return (
// Render the data
);
}
Subscribing to events with useEffect
Another use case is subscribing to events, such as window resize or keyboard events. With useEffect
, you can add event listeners and remove them when the component unmounts.
import React, { useEffect } from 'react';
function EventComponent() {
useEffect(() => {
const handleResize = () => {
// Handle resize event
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
// Component JSX
);
}
Updating UI based on changes in props
Another powerful use case for useEffect
is updating the user interface based on changes in dependencies. This allows you to dynamically modify the UI based on certain conditions or external data.
Let's explore an example of how useEffect
can be used to update the background color of calendar days based on events assigned to those days.
import { useEffect, useState } from "react";
interface Day {
date: string;
backgroundColor: string;
}
interface Event {
date: string;
name: string;
}
interface CalendarProps {
events: Event[];
days: Day[];
}
function Calendar({ events, days }: CalendarProps) {
const [calendarData, setCalendarData] = useState<Day[]>(days);
useEffect(() => {
// Update the background color of days with events
const updatedCalendarData = days.map((day: Day) => {
if (events.map(({ date }: Event) => date).includes(day.date)) {
return { ...day, backgroundColor: "yellow" };
} else {
return day;
}
});
setCalendarData(updatedCalendarData);
}, [events.toString(), days.toString()]);
return (
<div>
<h1>Calendar</h1>
<ul>
{calendarData.map((day: Day) => (
<li key={day.date} style={{ backgroundColor: day.backgroundColor }}>
{day.date}
</li>
))}
</ul>
</div>
);
}
In this example, we have a Calendar
component that takes in two props: events
and days
. The events
prop is an array of Event
objects, which contain information about each event, including the date and name. The days
prop represents an array of Day
objects, which contain the date and the background color of each day in the calendar.
Inside the Calendar
component, we initialize the state variable calendarData
using the useState
hook. It holds an array of Day
objects, initially set to the value of the days
prop.
We then use the useEffect
hook to update the calendarData
whenever the events
or days
dependencies change. This hook allows us to perform side effects in response to changes in props or state. In our case, we want to update the background color of the calendar days based on the events assigned to those days.
The effect function performs the following steps:
- It maps through the
days
array and checks if each day's date is included in theevents
array. It does this by using themap
function onevents
to extract the dates, and then checks if the current day's date is included in that array. - If the day's date is found in the
events
array, it creates a newDay
object with the same date and a background color of "yellow". Otherwise, it returns the originalDay
object. - The updated array of
Day
objects is then set using thesetCalendarData
function, which triggers a re-render of the component.
Finally, the Calendar
component renders the calendar UI based on the calendarData
state. It maps through the calendarData
array and renders a list item for each day, setting the background color dynamically based on the backgroundColor
property of each Day
object.
This example demonstrates how useEffect
can be used to update the UI based on changes in the events
prop. It allows us to dynamically modify the background color of the calendar days, providing visual feedback when events are assigned to specific dates.
Best practices
To ensure optimal usage of useEffect
and avoid common pitfalls, consider the following best practices and tips:
- Be mindful of the dependencies array and include only the necessary dependencies
- Use multiple
useEffect
calls to separate unrelated effects and improve code readability - Optimize performance by optimizing the side effect's code and minimizing unnecessary re-renders
- Be cautious with infinite loops by ensuring that the dependencies provided in the array are correctly updated
- Do NOT use arrays, objects or other types that inherit from them as dependencies, the reason being that even if their contents don't change, their reference will change between renders and as a result the effect callback will be called on every render regardless of whether or not it was actually needed
Conclusion
In this comprehensive guide, we've covered the essentials of how useEffect
can be used to handle side effects in different scenarios.
Hopefully by now, you're able to use the hook confidently in your apps and leverage its utility to write better React apps.