Master the useEffect Hook in React
If you have experience with using class components in React, you are probably familiar with the React component lifecycle. Before hooks were introduced to their functional counterparts, lifecycle methods were the only way to mount, update, and unmount components in an application using class components.
With the addition of the useEffect method from the hooks system, we now have an alternative to traditional methods such as componentDidMount, componentWillUnmount, as so on.
Why Lifecycle Methods?
Remember, the only required method inside a React class component is render(). Apart from that, any lifecycle methods we use are optional. So what is the purpose of using them?
In many cases, our applications require us to perform asynchronous operations in order to load data on the screen. This can potentially take a long time, and we need a way to re-render that data onto our components after the initial page has already loaded. Then imagine that once a user is finished with our component, we need a way for them exit from it.
In class components, traditional lifecycle methods help perform these tasks. A method such as componentDidMount() is used to load weather data, movies, or a list of barbershop locations once the application has already rendered. Methods such as componentShouldUpdate() checks before each render to see whether any changes on the page need to be executed. And finally, componentWillUnmount() resets and destroys our class component after it has performed its functionality.
The biggest advantage to utilizing useEffect() in a functional component instead of lifecycle methods is that it can single-handedly perform the duty of many of these functions by itself. Here’s how we do it!
In our example application, we will be designing a search bar functional component. Whenever a user types into the input search, our app will make an async GET request to the official Wikipedia API and return links to pages on the Wikipedia website pertinent to the search.
Since our focus in this article will be on the hook useEffect, we have already included useState and used it to track and control two pieces of state: the value of the input from the search bar, and a result that is set to an empty array. This results variable will be used later to display the results of our searchTerm.
We have also installed and imported the Axios library into our application since that will be what we use to make HTTP requests to the wikipedia API. Now, let’s import the useEffect hook from the React library and declare a tentative method call inside the component.
Remember, useEffect() is a special method that we declare inside of a functional component. It takes in two arguments, the first of which you see here: a callback function. This callback function tells the hook what actions to perform inside the component. As a second parameter, useEffect also accepts an optional array that is either empty or contains a piece of state. This array or lack of an array tells the hook when it should be called.
First, let’s take a look at what happens when we pass in an empty array.
Passing in an empty array is the equivalent of calling componentDidMount() inside a class. Everything within the callback function of useEffect will only run once at the beginning of the component’s “life cycle”. However, since this callback does not need to finish running by the time of the first render, this can be a great way to perform asynchronous actions.
However, if we want useEffect() to be invoked at the beginning AND anytime there is a change in state, we need to provide any variables declared within useState that we would like it to monitor. In this case, we want useEffect to run anytime there is a change in the value of our input term.
With the second argument set to an array containing searchTerm, we can now write some code that makes a GET request to the Wikipedia database whenever the user enters something different into the SearchBar component. Here are the endpoint and parameters to make that request using Axios:
We can try enclosing this HTTP request within the callback function of useEffect, and add the
awaitkeywords, but this will produce an error. Unforunately, we cannot add the
async keyword directly to the callback function that is passed to useEffect. However, there is an easy way around this.
Instead of making a request with Axios directly on the callback, we simply declare ANOTHER async function inside it, and thereafter implement it within the context of the outer callback function.
After we obtain an array of data as a response to our GET request, we implement our useState hook and set our
results state to the new array. Since this is an asynchronous function and we will likely implement it multiple times as a user searches for various terms, we can see why passing
[searchTerm] in as the second argument makes sense.
However, there are still a few issues with our implementation. Whenever a change occurs in the input value, a new request to the Wikipedia API is automatically sent. If we were to type in rapid succession, this would be a huge strain on the API servers and we want to avoid putting any unnecessary toll on them.
Instead of firing a request immediately on change, we can set a timeout to only make requests after a certain amount of time, say, 500 milliseconds. We want this setTimeout() to run whenever there is a change to the searchTerm state, so our code can once again go inside useEffect.
Now one final issue remains. If the user types something new in rapid succession, we need a way to reset our timeout with every change. In other words, we need to unmount the previous state of our component. This is where the final functionality of useEffect comes in: the return statement.
useEffect’s Cleanup Function
In the same way as its first argument, useEffect allows us to return a callback function inside of itself. This returned function is invoked FIRST whenever useEffect is called again and has a very important role: it “cleans up” or resets any potential side effects before being implemented again.
In other words, if we make a call to setTimout() within the callback function of our useEffect, we can return another callback function that clears that timeout for us! With the addition of some conditional statements, we can check to see whether we want to wait for a period of time in-between input changes. If there is currently a searchTerm to display in results, we want to call our async function WITHIN a setTimeout() AND return a callback function that clears the previous timeout set.
clearTimout() method within the return callback ALWAYS runs first whenever useEffect is called. That way, any previous timeouId will always be cleared before a new one is set. This is the beauty of the useEffect hook. It allows a true alternative to using lifecycle methods through its parameters and the return statement and becomes a powerful tool for constructing complex and dynamic components.
Our final refactor of useEffect() for our Wikipedia search bar component will look something like this. When new results are set with useState(), our component re-renders, filters the results to be displayed as JSX, and then passes this value into the component return statement.
I hope this provided a practical example of how versatile useEffect can be in functional components. They can be implemented either once after the first render, at every re-render, or at every re-render whenever a change in state occurs. When it is necessary, useEffect also returns a callback function that cleans up and resets any functionality before the next change in state.
For more information, I have personally gained a lot of insight on React hooks such as useEffect by simply consulting the React documentation and putting them into practice in real-world projects. I encourage you all to do the same and explore the possibilities with these newest tools in the React library!