The useEffect Hook allows you to perform side effects in your components.
Some examples of side effects are: fetching data, directly updating the DOM, and timers.
useEffect accepts two arguments. The second argument is optional.
useEffect(<function>, <dependency>)
Let's use a timer as an example.
Example:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});
return <h1>I've rendered {count} times!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);
But wait!! It keeps counting even though it should only count once!
useEffect runs on every render. That means that when the count changes, a render happens, which then triggers another effect.
This is not what we want. There are several ways to control when side effects run.
We should always include the second parameter which accepts an array. We can optionally pass dependencies to useEffect in this array.
Example
useEffect(() => {
//Runs on every render
});
Example
useEffect(() => {
//Runs only on the first render
}, []);
Example
useEffect(() => {
//Runs on the first render
//And any time any dependency value changes
}, [prop, state]);
So, to fix this issue, let's only run this effect on the initial render.
Example:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
}, []); // <- add empty brackets here
return <h1>I've rendered {count} times!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);
Example:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);
useEffect(() => {
setCalculation(() => count * 2);
}, [count]); // <- add the count variable here
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}
createRoot(document.getElementById('root')).render(
<Counter />
);
If there are multiple dependencies, they should be included in the useEffect dependency array.
Effect Cleanup
Some effects require cleanup to reduce memory leaks.
Timeouts, subscriptions, event listeners, and other effects that are no longer needed should be disposed.
We do this by including a return function at the end of the useEffect Hook.
Example:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
return () => clearTimeout(timer)
}, []);
return <h1>I've rendered {count} times!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);
Note: To clear the timer, we had to name it.