React Hooks - useEffect

| 6 min read

What is useEffect?

It is a tool in React that allows developers to perform side effects in function components. The hook is called every time a component is rendered, including the first time the component is rendered. It allows developers to perform a wide range of tasks, such as fetching data, setting up event listeners, and modifying the DOM.

This is useful for a wide range of tasks, such as fetching data, setting up event listeners, and modifying the DOM.

The most common example is fetching data from API, which usually looks like the one below.

import { useEffect, useState } from "react";

function App() {
	const [data, setData] = useState([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);

	useEffect(() => {
		const fetchData = async () => {
			try {
				const res = await fetch("https://my-api.com/data");
				const data = await res.json();
				setData(data);
			} catch (error) {
				setError(error);
			}
			setLoading(false);
		};

		fetchData();
	}, []);

	if (loading) return <p>Loading...</p>;
	if (error) return <p>{error.message}</p>;

	return (
		<div>
			{data.map((item) => (
				<p>{item.name}</p>
			))}
		</div>
	);
}

You also may see then() and catch() instead.

useEffect(() => {
  fetch("https://my-api.com/data")
    .then((response) => response.json())
    .then((data) => setData(data));
    .catch((error) => setError(error))
}, []);

What is the dependency array?

useEffect(() => {
	// Perform some side effect
}, []); // <-- see this array

This hook accepts a 2nd argument, which is the dependency array. It is to control when the hook is called.

If the array is empty like the example above, it will only be called once when the component is first rendered.

Both hooks have a value in their arrays

Let’s see what will happen if there are two useEffect hooks with different values in the array.

useEffect(() => {
	// Perform some side effect
}, [value1]);

useEffect(() => {
	// Perform some side effect
}, [value2]);

Both hooks were running every time the count value is changed.

One hook has both values in the array

Let’s see what happens if the first useEffect has value1 and value2 while the 2nd useEffect has value2.

useEffect(() => {
	// Perform some side effect
}, [value1, value2]);

useEffect(() => {
	// Perform some side effect
}, [value2]);

Notice when I clicked the 2nd button, both useEffects were called? It is because the first useEffect has count and count1 in its dependency array

The 2nd hook doesn’t have an array

Let’s see what happens if the first useEffect has a value in the array while the 2nd useEffect doesn’t have an array.

useEffect(() => {
	// Perform some side effect
}, [value1]);

useEffect(() => {
	// Perform some side effect
});

As you can see, by clicking the first button, both useEffects were called. Imagine this as a huge app that does many things, the useEffect without an empty array will run on every render. Action A will run the useEffect. Action B will run the useEffect as well. And more actions will run the useEffect! This will have many unnecessary renders and cause performance issues.

My rule of thumb is always to add an empty array in the useEffect. At least, you can know and make sure the component will only run once after the initial render.

Test Code

This is the code that I wrote for this blog. If you are interested in playing around with useEffect hook, click the box below.

Code
import { useEffect, useState } from "react";
import "./App.css";

function App() {
	const [count, setCount] = useState(0);
	const [count1, setCount1] = useState(0);

	function handleClick() {
		setCount((prev) => prev + 1);
	}

	function handleClick1() {
		setCount1((prev) => prev + 5);
	}

	useEffect(() => {
		console.log(`the count: ${count}`);
	}, [count]);

	useEffect(() => {
		console.log(`the count1: ${count1}`);
	}, []);

	return (
		<main>
			<h1>Hello</h1>
			<p>count: {count}</p>
			<p>count1: {count1}</p>
			<button type="button" onClick={handleClick}>
				click for count
			</button>
			<button type="button" onClick={handleClick1}>
				click for count1
			</button>
		</main>
	);
}

export default App;

Build your hook

You can also define your hooks in separate files. These files should be in the hooks folder. The file name usually starts with use with a description. e.g. useFetchApi or useFetchData or a similar naming convention.

A couple of advantages:

  • Call the hook in a specific component that needs the fetched data
  • Less visual clutter for the component file
  • Reusable

It is very similar to how would you write in the App or other components. Instead of returning the component structure, return the fetched information. And the file path should look like src/hooks/useFetchApi.jsx

import { useEffect, useState } from "react";

const useFetchApi = () => {
	const [data, setData] = useState([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);

	useEffect(() => {
		const fetchData = async () => {
			try {
				const res = await fetch("https://my-api.com/data");
				const data = await res.json();
				setData(data);
			} catch (error) {
				setError(error);
			}
			setLoading(false);
		};

		fetchData();
	}, []);

	return { data, loading, error };
};

export default useFetchApi;

Go to the component and write the codes below.

import useFetchApi from "../hooks/useFetchApi";

function Something() {
  const { data, loading, error } = useFetchApi()

  return (
    // render the page with fetched information!
  )
}

export default Something;

Resources

Thank you!

Thank you for your time and for reading this!