Introduction
In the previous post, we looked at the useState() Hook that adds state to a functional component. We learned how to initialize, update and access state variables in a functional component using useState() Hook.
In this post, we'll focus on the useEffect() Hook that let us perform side effects in functional components. We'll also understand how this particular hook can be used to mimic the behavior of componentDidMount(), componentWillUnmount() and componentDidUpdate() lifecycle methods.
Prerequisites
- An understanding of the useState() Hook
useEffect() Hook
The operations such as data fetching, manual DOM mutations, logging, setting up a subscription and unsubscribing it are all examples of side effects. These side effects are too early to be dealt while the component is being rendered to the screen. Therefore, the class components are provided with lifecycle methods such as componentDidMount
, componentDidUpdate
and componentWillUnmount
that run after React has updated the DOM.
However, functional components don't have such lifecycle methods. Thus, useEffect
Hook was introduced that let us perform side effects in functional components.
The syntax for useEffect Hook is as below:
useEffect(function, [dependencies]);
// first argument is a function where we pass our side effect
// second argument is a dependencies array. it is an optional argument
// with no dependencies array present, useEffect runs after every render
Now that we are clear with the syntax, lets take a look at the following class based component that logs a message to the console after the component is rendered to the screen and on any state update
import React, { Component } from "react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
age: 26
}
}
componentDidMount() {
console.log(`I am ${this.state.age} years old`);
}
componentDidUpdate() {
console.log(`I am ${this.state.age} years old`);
}
render() {
return (
<div>
<p>I am {this.state.age} years old</p>
<button onClick={() => this.setState({
age: this.state.age + 1
})}>Celebrate Birthday</button>
</div>
);
}
}
As per the above code block, after the component gets rendered to the screen, componentDidMount
gets called which logs a message to the console. When the button is clicked, the component re-renders with the updated age value and componentDidUpdate
gets called which logs a message to the console.
It is evident from the above code block that duplicate code is used in both the lifecycle methods. This is because in many cases you want to perform the same side effect regardless of whether the component was just mounted or has been updated. React class components don't have a lifecycle method that allows a particular code to run after every render.
Now, lets take a look at the functional component using useEffect Hook to achieve the same thing
import React, { useState, useEffect } from "react";
export default function App() {
const [age, setAge] = useState(26);
useEffect(() => {
console.log(`I am ${age} years old`);
});
return (
<div>
<p>I am {age} years old</p>
<button onClick={() => setAge(age + 1)}>Celebrate Birthday</button>
</div>
);
}
The above code performs the same thing that the class component does but with lesser code. Here, we use the useState Hook to initialize and update the age variable.
Now, lets understand the useEffect
Hook
In order to perform side effects in functional component, you first need to import
useEffect
Hook from React.In the App component above, you can see that the State Hook is used to initialize the
age
variable.useEffect
Hook is defined after the State Hook and a function to log theage
variable is passed to it.The Effect Hook is defined inside the component so that it can easily access the
age
variable or anyprops
passed to the component.After React renders the component to the screen, it moves to the
useEffect
Hook and runs it which logs theage
variable to the console.When you click the button,
age
variable is updated that leads to re-rendering of the component with the updated value. This triggers the effect to run again.The
useEffect
Hook runs both after the first render and after every update (on state variable change and props change) because there is no dependencies array present as the second argument.
Thus, you can see that the functional component with useEffect
Hook is able to achieve the same thing in a single code block which class component achieved in two lifecycle methods.
Now, you got a basic understanding of how useEffect
Hook runs. But, without the dependencies array, it is seen that the Effect Hook runs after every render.
There are cases where we don't want it to run after every render as it can lead to undesirable results or performance issues in many cases. In such scenarios, you can make use of dependencies array to determine when useEffect
should run again once it has run after the first render. Any change in value of dependencies present in the array triggers the useEffect
Hook to run again.
useEffect() with dependencies array
The following code block introduces the dependencies array in the Effect Hook
import React, { useState, useEffect } from "react";
export default function App() {
const [age, setAge] = useState(26);
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`I am ${age} years old`);
}, [age]);
return (
<div>
<p>I am {age} years old</p>
<button onClick={() => setAge(age + 1)}>Celebrate Birthday</button>
<p>The guest count is {count}</p>
<button onClick={() => setCount(count + 1)}>Add Guest</button>
</div>
);
}
Here, we are having two state variables age
and count
. The dependencies array has age
variable present in it. So, once the Effect hook runs after the first render, it will now run only when the age
variable is updated. Therefore, if you click the button that updates the count
variable, it will not trigger the effect to run. But when the button that updates the age
variable is clicked, the effect will run. Therefore, the effect now runs only when age
is updated and not after every render.
So far, you've looked at the side effects without cleanup. But there are certain side effects which do require clean up. Some examples include setting up a subscription to some external data source which also needs to be cleaned up so that no memory leak is introduced or setting up a timer and then clearing it after that component is destroyed.
useEffect() with cleanup
Now, lets take a look at the class component where setting up a timer is typically done in componentDidMount
method and clean up is done in componentWillUnmount
method
import React, { Component } from "react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
timer: 0
}
}
componentDidMount() {
this.id = setInterval(() => {
this.setState({
timer: this.state.timer + 1
})
}, 1000);
}
componentWillUnmount() {
clearInterval(this.id);
}
render() {
return (
<div>
<p>Timer: {this.state.timer}</p>
</div>
);
}
}
componentDidMount
gets executed after the component is rendered to the screen thereby setting up a timer. This timer continues to run till the component is in scope. If the component is about to be unmounted and destroyed, componentWillUnmount
gets executed immediately before unmount and any necessary cleanup is performed such as clearing up the timer in the above example.
Now, lets take a look at an equivalent functional component. The function passed to the useEffect hook can return a function that acts as a clean up script. This script runs when the component is about to be unmounted and before every consecutive run of the Effect hook after the first run.
import React, { useState, useEffect } from "react";
export default function App() {
const [timer, setTimer] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setTimer(prevValue => prevValue + 1)
}, 1000);
return () => {
// cleanup script
clearInterval(id)
}
},[]);
return (
<div>
<p>Timer: {timer}</p>
</div>
);
}
In the above example, the Effect hook returns a cleanup function. Since, the dependencies array is empty, the effect does not depend on any changes in the state value or props value and therefore it never re-runs. It will always have the initial value of state and props.
Since the Effect hook is restricted here to run only once, the clean up script gets executed only when the component is about to be unmounted. Therefore using the Effect Hook in this manner is equivalent to componentDidMount
and componentWillUnmount
lifecycle methods.
You can have more than one Effect Hook in your component.
Conclusion
In this post, you got an understanding of the useEffect() Hook. You learned its syntax and how it is used to perform side effects in a functional component. You also learned about the dependencies array that restricts the Effect hook to run on every render. You learned how related code gets split between lifecycle methods in class component whereas Hooks in functional component let us split the code based on what it is doing and groups related code together.
Thanks for taking the time to read this post. I hope this post helped you!!๐๐ If you liked it, please share.
It would be great to connect with you on Twitter. Please do share your valuable feedback and suggestions๐
Top comments (0)