Use useEffect like a pro — A complete guide to work with useEffect in React
Introduction
React is one of the most popular front-end libraries in the Javascript community. React is a component-based frontend library that makes the process easier for you to create complex interactive UIs. But in order to work with React you have to understand some basic concepts about React. One of the concepts is called React hooks.
Before React 16.8 we had to use class components to create components. But with React 16.8 the react dev team introduced functional components and with functional components, the new concept called hooks came to the scene.
Basically hook is a function that let you “hook into” React state and lifecycle features from function components.
By default, React has some built-in hooks like useState, useEffect, useLayoutEffect, etc. But in this article, I will give you a complete guide of useEffect hook which is really an important hook you should understand correctly if you are learning React.
Let’s dive into Use Effect
In basic words, useEffect allows you to add side effects to your components. But what is a side effect?
When you are working with a component you will need to fetch some data from an external API, or you need to subscribe to another service, or you need to interact with browser APIs. Since these exist in the outside scope these types of things we call side as side effects. UseEffect allows us to perform these side effects when something happens. By default, the use effect runs after the first render of the component and after every update of the component. So let's dive into some pieces of code to see how we can use useEffect.
The very first thing you need to do is import the useEffect hook from react.
import { useEffect } from "react";
By default, useEffect accepts two arguments. The first one is a callback function and the second parameter is a dependency array which is optional. I’ll come to this dependency array in a while. But let's see the first type of usage in useEffect.
Usage type I
If you need to do something once when the component is mounted then you can write a useEffect as follows.
useEffect(() => {
console.log("This will run only once when the component is mounted");
}, []);
Here what I have done is I have passed the call back function and as the second argument an empty array. The empty array is the array we called as the dependency array. When we add some dependencies to it, the use effect will run every time when there’s a change in the dependencies. But since here the dependency array is empty, the use effect will be triggered only once when the component is mounted. You can confirm it by checking the browser console and there will be a one-log in the console.
Usage Type II
Let’s say you have some state and when the state changes you need to do something in the application. For that, we can pass that state to the dependency array. So whenever that state changes the useEffect will get triggered. Check the following snippet.
const [total, setTotal] = useState();
useEffect(() => {
console.log(`Total changed to: ${total}`);
}, [total]);
So here, I have a state called total and I have added that to the dependency array. So whenever you change the value of the total using setTotal this useEffect will run and logs the new total to the console.
You can pass as many as dependencies you need to this dependency array as the following.
const [total, setTotal] = useState();
const [subTotal, setSubTotal] = useState();
useEffect(() => {
console.log(`Total value: ${total}`);
console.log(`Sub Total value: ${subTotal}`);
}, [total, subTotal]);
Here when the total or sub-total changes the useEffect will run.
Usage Type III
Since the dependency array is optional you can only pass the callback function to the useEffect hook without the dependency array as follows.
useEffect(() => {
console.log("Use effect ran without the depency array");
});
But when you do this, this effect will run every time when the component re-renders. So be careful when you use useEffects inside your components, since it can affect the performance of the application.
Cleaning up the effect
Let's say when the component mounts you are subscribing to a service and you need to unsubscribe from it when the component unmounts. But how can you execute a code only when the component unmounts? For that, you can add a return callback inside the useEffect and the function inside the return statement will be get executed when the component gets unmounted from the React tree.
useEffect(() => {
console.log("Component mounted");
return () => {
// This is where you can define the things you need to perform
// when the component gets unmounted.
console.log("Component unounted");
};
}, []);
Data Fetching inside useEffect
That’s how you can useEffect and I will show you an example of data fetching from an external endpoint using use effect using some best practices that you can use inside your components.
import axios from "axios";
const [todos, setTodos] = useState();
const [error, setError] = useState();
useEffect(() => {
let isUnmounted = false;
axios
.get("https://jsonplaceholder.typicode.com/todos/")
.then((res) => {
if (!isUnmounted) {
setTodos(res.data);
}
})
.catch((e) => {
if (!isUnmounted) {
setError(e.message);
}
});
return () => {
isUnmounted = true;
};
}, []);
In the above snippet, I have used a REST API which returns an array of todos. But when I fetch these todos inside useEffect first I declared a variable called isUnmounted and I set that to false. Once the REST API returns the data before I set them to the state I tracked if it’s unmounted using that variable. So if the component is not unmounted and only then the data will be assigned to the state. Since I set that variable to true inside the return statement, if the component gets unmounted then this variable will be changed to true and the data won’t be set to the state.
I checked this because when we are fetching some data from an external resource it will generate a promise which will take some time to resolve. But while the promise is being resolved the component could have been unmounted from the tree. If the component has unmounted there’s no point in setting that data to the state. So we can use this kind of approach to handle that scenario.
I will show you another approach that can be used to handle the same scenario. For that, I’m using the AbortController in javascript. If you need to learn more about AbortController I will leave the link to it at the end of the article.
const [todos, setTodos] = useState();
const [error, setError] = useState();
useEffect(() => {
const controller = new AbortController();
axios
.get("https://jsonplaceholder.typicode.com/todos/", {
signal: controller.signal,
})
.then((res) => {
setTodos(res.data);
})
.catch((e) => {
setError(e.message);
});
return () => {
controller.abort();
};
}, []);
In the above example, I have declared a new instance from AbortController class. It has an attribute called signal and we can pass that signal into the Axios to track the network request. The controller has a method called abort to abort the request. So I added that to the cleanup statement. So if the component gets unmounted that request will be aborted and you can catch the error from the catch block and the todos will be only set to the state only if the component has mounted. This does the same thing as the above example. But the code looks cleaner in this approach.
So this is the end of the article and those are the things that I need to point out for you when you are using useEffect. If you learned something give me a clap and follow me for the next articles.
AbortController Docs:- https://developer.mozilla.org/en-US/docs/Web/API/AbortController