useEffect Demystified: A Beginner’s Guide to React’s Side Effect Hook

The Atypical Developer
4 min readDec 28, 2022

--

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:

  1. Getting Started with React: Setting Up Your First Environment #1
  2. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. If you’re using an effect to update the state of a component, be sure to wrap the state update in a setTimeout or setImmediate 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.

--

--

No responses yet