useEffect Demystified: A Beginner’s Guide to React’s Side Effect Hook
useEffect
is a hook in React that allows you to perform side effects in function components. It is a way to handle lifecycle events and state updates in your component, and it is intended to replace lifecycle methods such as componentDidMount
, componentDidUpdate
, and componentWillUnmount
.
Previous articles of the series:
- Getting Started with React: Setting Up Your First Environment #1
- The useState Hook: A Deep Dive into React State Management #2
The useEffect
hook is primarily used to perform side effects in function components. These side effects can include tasks such as making network requests, setting up subscriptions, and manipulating the DOM.
Hook’s structure
The structure of useEffect
is as follows:
useEffect(() => {
// code to run on effect
}, [dependency1, dependency2, ...]);
useEffect
is a function that takes a callback function as its first argument. The callback function contains the code that should be run on each effect. The second argument is an optional array of dependencies. When one or more of the dependencies change, the effect will be re-run. If the array is omitted, the effect will be run on every render.
Here is an example of using useEffect
to log a message to the console every time the component renders:
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
console.log('The component has rendered');
});
return <div>My component</div>;
}
Here is an example of using useEffect
to only log a message to the console when a specific prop changes:
import { useEffect } from 'react';
function MyComponent(props) {
useEffect(() => {
console.log('The "message" prop has changed');
}, [props.message]);
return <div>My component</div>;
}
In this example, the effect will only be run when the message
prop changes. If the message
prop is not provided, the effect will not be run at all.
Real life examples
One common use case for useEffect
is to fetch data from an API and update the component's state with the response. For example:
import { useEffect, useState } from 'react';
function ExampleComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/random-url')
.then(response => response.json())
.then(data => setData(data));
}, []);
return <div>{data && data.message}</div>;
}
In this example, the useEffect
hook is called with an anonymous function that fetches data from an API and updates the component's data
state. The second argument to useEffect
is an array of dependencies, which tells React to only run the effect if one of the values in the array has changed. In this case, the empty array means that the effect will only run once, when the component mounts.
Another common use case for useEffect
is to set up subscriptions and perform cleanup when the component unmounts. For example:
import { useEffect, useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, [count]);
return <div>{count}</div>;
}
In this example, the useEffect
hook is called with an anonymous function that sets up an interval to increment the count
state every second. The effect also returns a function to clear the interval when the component unmounts. The count
state is included in the dependencies array, which means that the effect will run every time the count
state changes.
useEffect caveats
Some caveats to keep in mind when using useEffect
include:
useEffect
is called after every render, so you need to be careful about the performance of the code you put inside it. For example, if you have an expensive operation that doesn't need to be performed on every render, you can use the[]
dependency array to ensure that the effect only runs when certain values change.- If you have multiple effects that depend on the same values, you should consider grouping them together in a single effect. This can help avoid unnecessary re-renders and improve performance.
- Be mindful of the order in which effects are defined. If an effect depends on the values of another effect, it should be defined after that effect.
- If you need to perform an action when a component unmounts (e.g. cancelling a network request), you can return a function from the effect that performs the necessary cleanup.
- If you’re using an effect to update the state of a component, be sure to wrap the state update in a
setTimeout
orsetImmediate
call to avoid causing an infinite loop.
Practice task
Create a component that displays the current time in the format “hh:mm:ss” and updates the time every second.
Specifications:
- The component should display the current time in the format “hh:mm:ss” (e.g. “13:45:00”).
- The time should update every second.
- The component should use the
useEffect
hook to update the time. - The component should include a button that allows the user to pause and resume the time updates.
- When the time updates are paused, the time should remain static and not change.
- When the time updates are resumed, the time should continue updating from the current time.
Solution (spoiler alert!)
import React, { useState, useEffect } from 'react';
function TimeDisplay() {
const [time, setTime] = useState(new Date());
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
const interval = setInterval(() => {
if (!isPaused) {
setTime(new Date());
}
}, 1000);
return () => clearInterval(interval);
}, [isPaused]);
const handlePause = () => {
setIsPaused(!isPaused);
};
return (
<div>
<p>{time.toLocaleTimeString()}</p>
<button onClick={handlePause}>
{isPaused ? 'Resume' : 'Pause'}
</button>
</div>
);
}
The useEffect
hook is used to set up an interval that updates the time every second. The isPaused
state value is included in the dependency array to ensure that the effect is only re-run when the pause state changes. The effect also includes a cleanup function that clears the interval when the component unmounts.