ReactJS effect hook demystified
What is the effect hook?
The effect hook functions in the same way as the React life cycle methods componentDidMount, componentDidUpdate and componentWillUnmount for class components. The effect hook can be used for managing side effects in a functional component. The effect hook runs after React has rendered the functional component.
The example code below shows the difference between using React class lifecycle methods and effect hook in a functional component:
Class example:
class PriceControl extends React.Component { constructor(props) { super(props); this.state = { price: 0 }; } componentDidMount() { document.title = `Price: ${this.state.price}`; } componentDidUpdate() { document.title = `Price: ${this.state.price}`; } render() { return ( <div> <p>The current price is {this.state.price}</p> <button onClick={() => this.setState({ price: this.state.price + 1 })}> Increase Price </button> </div> ); } }
Functional component example:
import React, { useState, useEffect } from 'react'; function PriceControl() { const [price, setPrice] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `Price: ${price}`; }); return ( <div> <p>The current price is {price}</p> <button onClick={() => setPrice(price + 1)}> Increase price </button> </div> ); }
In the provided code 'useEffect' method is used to pass in a callback. This callback will be executed when the component is mounted like 'componentDidMount' and when it is updated like 'componentDidUpdate'. So the document title is changed first when the component mounts then each time when component updates.
Conditional effect:
The functional component can skip the effect conditionally after the first render by passing a second argument to the 'useEffect'. This second argument which is an array will be compared from previous variables that were passed and only execute the effect if the variables have different values.
useEffect(() => { // Update the document title using the browser API document.title = `Price: ${price}`; }, [price]);
In the sample code above the 'useEffect' is passed in a second argument which is an array containing 'price' variable. So after the first render this effect will only execute the callback if the value of prices changes over each render.
Cleanup vs non-cleanup:
The code inside 'useEffect' callback shown previoulsy changes the document title which doesn't cause any memory leaks and it doesn't need to be cleaned up in any way after the component unmounts. But in certain cases it may be necessary to cleanup code after each render to avoid memory leaks or bugs. For this reason a function can be returned from the 'useEffect' callback that will execute when the component unmounts and after each render.
useEffect(() => { let interval = setInterval(()=>{ for(let i =0; i<1000; i++){ enemyLocations.push([Math.random()*10, Math.random()*10]); } }, 500); return ()=>{ if(interval!==undefined){ clearInterval(interval); } }; });
In the sample code above each time the component is rendered it calls 'setInterval'. The callback inside 'setInterval' will populate the 'enemyLocations' array every 500 milliseconds. The 'enemyLocations' array is part of the functional component. But if we don't clear the interval after every render then 'setInterval' will never stop and multiple 'setInterval' callbacks will add 1000 locations every 500 milliseconds which will cause memory leak. So by returning a function from 'useEffect' we can clear the interval after every render and when the component unmounts.
Summary:
- The 'useEffect' API can be used to manage side-effects similar to componentWillMount, componentDidUpdate and componentWillUnmount life cycle methods.
- The effect can be executed conditionally by passing in a second argument to 'useEffect' containing an array of variables to monitor.
- The code inside 'useEffect' can be cleanedup by returning a function which will cleanup any subcribtions or memory after every render and when the component unmounts.