Understanding how useEffect works: A comprehensive guide

Hikmah Yousuph
Nur: The She Code Africa Blog
5 min readJun 9, 2023

Outline

· Introduction
What is useEffect?
React lifecycle and useEffect
· How useEffect works
Code Structure
· When to use useEffect
When performing side effects
When updating state
When cleaning up after component
When controlling the component rendering
· Conclusion

Hooks are important in React functional components and can come in handy when building React applications. There are a number of hooks in React but one of the most popular hooks that is used in many React applications is the useEffect hook. In this article, we’ll look at how the useEffect hook works.

Introduction

What is useEffect?

useEffect is a hook in React functional components that allows you to perform side effect functions. Side effect functions are actions that affect the states or behaviour of the component or outside the scope of the component, like when fetching data from an API, using event handlers, and modifying the DOM among others.

React lifecycle and useEffect

If you’re transitioning from class-based components to function-based components in React, you can think of useEffect as an equivalent to ComponentDidMount, ComponentDidUpdate, and ComponentWillUnmount methods.

In useEffect, there are three phases in React lifecycle, from initiation to destruction:

i. Mounting: When a component loads for the first time or is reloaded, it is said to have mounted. The component is initialized and inserted into the DOM and its state is set. This phase is similar to the ComponentDidMount method in class-based components.

ii. Updating: This is the next phase in the lifecycle, equivalent to ComponentDidUpdate. The component updates when there is a change in the state or props.

iii. Unmounting: In this final phase, the component’s state is destroyed through a cleanup function. A cleanup function is used to remove the component from the DOM, similar to ComponentWillUnmount in class-based components.

How useEffect works

Code Structure

Below this the structure of a typical useEffect function:

import React, { useEffect } from 'react';

function Component() {
useEffect(() => {
// Side effect code here
// This code will run after every render
// Optional cleanup function
return () => {
// Code to clean up after the effect
// This code will run before the next effect runs
};
}, [/* dependency array */]);
// Component code here
// This code will run after the side effect code
return (
// JSX to render the component
);
}

Usually, useEffect takes two arguments: a function and an array.

The first argument is a callback function that runs after the component renders. This function runs every time the component re-renders, depending on the second argument, the array. The cleanup function is optional and is only needed if the useEffect function is unmounting and requires cleanup.

The second argument, the dependencies array, controls the re-rendering of the component. You can add states and props to this array to control the component’s rendering. If you leave the array as an empty array, the component will only render once. Not including at least an empty array can cause the component to mount infinitely, which is bad. If you include one or more dependencies in the array, useEffect will only run if any of the dependencies change.

When to use useEffect

When performing side effects

Side effect functions that affect the component or the outside world state or behaviour are done in useEffect functions. These functions such as fetching data from an API, setting a timer, manipulating the DOM, etc, can only happen after the component is rendered.

import { useState, useEffect } from 'react';

function Component() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('<https://api.apiwebsitename.com/>')
.then(response => response.json())
.then(data => setData(data))
.catch(error => console.error(error));
}, []);
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}

This code snippet shows data being fetched from an API only once due to the empty dependency array. Initially, the data state was an empty array, but after the side effect function in the useEffect, the state gets updated.

When updating state

A change in a state can be used to trigger the update of another state with the help of useEffect.

import { useState, useEffect } from 'react';

function Component() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count is currently at ${count}`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In the above example, the document title gets updated with every click event, incrementing the count state and triggering the useEffect with each change in the count state.

When cleaning up after component

For example, when setting a timer or an interval, removing an event listener, or canceling a subscription, cleaning up with useEffect is important to prevent memory leaks that can cause issues in the application.

import { useEffect } from 'react';

function Component() {
useEffect(() => {
const timer = setTimeout(() => {
console.log('Timeout completed.');
}, 3000);
return () => {
clearTimeout(timer);
console.log('Timeout cleared.');
};
}, []);
...

In the provided example, the timeout function is called after 3 seconds, after which it is cleared out of the memory when the component unmounts.

When controlling the component rendering

Unlike when a state is updated, this, that is when controlling the component state, determines whether or not the component should be rendered at all. States used return boolean value while the states used when updating ranges from strings to objects.

import React, { useState, useEffect } from "react";

function Component(props) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch("<https://api.apiwebsitename.com/>");
const json = await result.json();
setData(json);
setIsLoading(false);
}
fetchData();
}, [props.userId]);
return (
<div>
{isLoading ? (
<div>Loading...</div>
) : (
<>
<h1>{data.name}</h1>
<p>Your email is {data.email}.</p>
</>
)}
</div>
);
}

A common example of this is when a user profile is being fetched. If there is a userId or the userId exists, the component displays, if not, the component doesn’t.

Conclusion

Although useEffect can be challenging for beginners, but with practice, it becomes easier to understand and use. By understanding how useEffect works, you can effectively execute your React application, enhancing its behaviour and function.

Overall, useEffect is an important hook and should be harnessed to optimise the functionality and performance of your React components.

--

--

Hikmah Yousuph
Nur: The She Code Africa Blog

Software developer skilled in creating articles all things tech. Passionate about problem-solving, tech writing, fantasy novels, and keeping up with tech news.