React hooks were introduced in React 16.8 and they help you use state management and component lifecycle methods in functional components, without having to use Class components.
The useEffect
hook behaves similar to componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle hooks combined. It allows you to perform any (side)effects, such as API calls, without blocking the UI render.
Usage
The syntax is straightforward like most other hooks. You call useEffect(), passing it a function and an optional dependency array.
No Dependency Array
import { useEffect } from 'react';
// called on every render
useEffect(() => {});
useEffect will be called on the first render of the component, and then on every subsequent render, if you don't provide a dependency array. This is not recommended, unless you really need this function to run on every single render.
With Dependency Array
Every time any item in the dependency array changes, the useEffect will be invoked, and if this function mutates state, the UI will re-render.
// called when watched values change
useEffect(() => { //code }, [valWillChange, otherVals,...etc]);
Run only once
If you want useEffect to run only once, pass it an empty dependency array:
useEffect(() => { }, [])
.
This will only run on initial component render.
Example with memory leak
Here's a trivial example that sets a timeout and logs a value to the console after the timeout.
useEffect(() => {
// will log the count to console after 2.5 seconds
setTimeout(() => {
console.info(`count is ${count}`);
}, 2500);
}, [count]);
While this code works, there is a problem here that you might not notice at first glance.
I'm not clearing the timeout!
So this code will create a new timeout on every call, without clearing any of them, leading to memory leaks and slow performance.
Better example by returning a cleanup function
To fix this, useEffect can return a cleanup function(similar to componentWillUnmount
) where you can unsubscribe from your data source, de-register event listeners or clear the timeout like in this example.
To fix the memory leak in the previous example, simply clear the timeout in the return function of useEffect.
useEffect(() => {
// will log the count to console after 2.5 seconds
const timer = setTimeout(() => {
console.info(`count is ${count}`);
}, 2500);
// the return cleanup function, same concept as componentWillUnmount
return () => clearTimeout(timer); // clear the timeout
}, [count]);