The useEffect Hook
If you’ve written React class components before, you should be familiar with lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
. The useEffect
Hook is all three of these lifecycle methods combined. It's used for side effects (all things which happen outside of React) like network requests, managing subscriptions, DOM manipulation, setting up event listeners, timeouts, intervals, or local storage, etc.
useEffect
functions run after every rerender by default.
It doesn't matter what caused the render like changing the state, or maybe new props, the effect will be triggered after rendering.
Setting the title of the page will also be a side effect.
useEffect
takes a callback function, we can tell useEffect
when the code we want to be executed with the second argument. This will control the effect.
For the second argument, we can use useEffect
in three different ways:
1. useEffect without a Dependency Array
To make this clear for you I want to make an analogy, imagine you have a car, and you never need to buy gas or even start the car, it just goes anywhere you want. Now, back to code!
This renders every time our app renders and at initial render. But we don't want to render each time, this can cause an infinite loop and we should avoid this.
We need to optimize our components. We can pass a list of dependencies. The dependency will trigger an effect on the change of the dependencies.
Let's see it in a simple example.
In our example, we have two states: count
and isOn
. We are rendering these with our button
and h1
tags. When the button gets clicked, we are setting the isOn
state to the opposite of its state.
For the purpose of this example, we are setting useEffect
hook and changing our document title to our isOn
's default value(false).
With our console.log
, we can see that we rerender our component with our initial render and whenever we click the button. Because we don't have any array dependency.
2. useEffect with an Empty Dependency Array
Now, for the second scenario, we have a car but first, we need to start the car, after that no need to update anything, just one time start the car.
This only runs once when the component is mounted or loaded.
It looks exactly like the behavior of componentDidMount
in React classes. But we shouldn't compare with React class components.
3. useEffect with a Non-empty Dependency Array
The last analogy is the real-life scenario, we have a car and we need to buy gas, change the oil, etc. We depend on other stuff to start our car. So, we need to refill gas to drive the car.
If the variable is inside this array, we will trigger this effect only when the value of this variable changes, and not on each rerender. Any state or props we list in this array will cause useEffect
to re-run when they change.
We can put our variables inside the dependency array from our component like any variables that we want for; for example, state variables, local variables, or props. They adjust the array of dependencies.
We have just changed one line of code from the previous example and changed useEffect
a little, we will not increase our count with the button click. However, we will trigger our effect whenever the useEffect
triggers. Let's see what will happen.
We are in an infinite loop; but why? React rerenders our component when the state changes. We are updating our state in our useEffect
function, and it's creating an infinite loop.
I think no one wants to stuck in a loop; so, we need to find a way to get out of the loop and only run our function whenever our isOn
state changes. For that, we will add our dependency array and pass our isOn
state.
The array of variables will decide if it should execute the function or not. It looks at the content of the array and compares the previous array, and if any of the value specified in the array changes compared to the previous value of the array, it will execute the effect function. If there is no change, it will not execute.
It seems like working, at least we got rid of the infinite loop, when it updates count
it will rerender the component. But if you noticed, we start counting from 1 instead of 0. We render first at initial render, that's why we see 1. This effect behaves as a componentDidMount
and componentDidUpdate
together. We can solve our problem by adding an if
condition.
This will only render at the first render, after that when we click the button, setIsOn
will be true. Now, our code looks like this.
Okay, now it starts from 0. If you're checking the console, you may see a warning:
We will not add count
inside our dependency array because if the count changes, it will trigger a rerender. This will cause an infinite loop. We don't want to do this, that's why we will not edit our useEffect
. If you want, you can try it out.
useEffect
Cleanup
useEffect
comes with a cleanup function that helps unmount the component, we can think of it is like componentWillUnmount
lifecycle event. When we need to clear a subscription or clear timeout, we can use cleanup functions. When we run the code, the code first will clean up the old state, then will run the updated state. This can help us to remove unnecessary behavior or prevent memory leaking issues.
We have defined a setInterval
method inside our useEffect
hook, and our interval will run in the background. We are passing a function inside setInterval
and it will update our count
piece of state every second.
Our useEffect
hook is only gonna run one time because we have our empty dependency array.
To clean up our hook, we are creating our return
function, getting our interval id, and passing inside our clearInterval
method.
note
- We can use multiple useEffect's in our application.
- We cannot mark useEffect as an async function.
- React applies effect in the order they are created.
- We can make API calls to React in four different ways:
- Call fetch/Axios in your component
- Make another file and store your API calls.
- Create a reusable custom hook.
- Use a library like react-query, SWR, etc.
We will use fetch
in our application for simplicity. Now, ready to move on with our final demo app? Time to combine everything you have learned with a real-life application. This will be fun!!! 😇