Curriculum
Course: React
Login

Curriculum

React

Text lesson

useEffect

The useEffect Hook enables you to handle side effects in your components, such as fetching data, updating the DOM, and managing timers.

It takes two arguments, with the second being optional: useEffect(<function>, <dependency>).

For example, let’s use a timer.

Example:

Use setTimeout() to count 1 second after the initial render.

import { useState, useEffect } from "react";
import ReactDOM 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>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

But wait! The timer keeps counting repeatedly because useEffect runs on every render, causing a new effect to trigger each time the count changes.

To control when side effects run, we should always provide a second parameter to useEffect, which is an array. You can include dependencies in this array to specify when the effect should be executed.

Example

1. No dependency passed:

useEffect(() => {
 //Runs on every render
});

Example

2. An empty array:

useEffect(() => {
 //Runs only on the first render
}, []);

Example

3.Props or state values:

useEffect(() => {
 //Runs on the first render
 //And any time any dependency value changes
}, [prop, state]);

To resolve this issue, let’s ensure the effect runs only on the initial render.

Example:

Run the effect only on the initial render.

import { useState, useEffect } from "react";
import ReactDOM 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>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

Example:

Here’s an example of a useEffect Hook that depends on a variable: if the count variable updates, the effect will be triggered again.

import { useState, useEffect } from "react";
import ReactDOM 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>
    </>
 );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

All multiple dependencies should be listed in the useEffect dependency array.

Effect Cleanup

Some effects need cleanup to prevent memory leaks. Timeouts, subscriptions, event listeners, and other effects that are no longer required should be disposed of. This is done by including a return function at the end of the useEffect Hook.

Example:

Clean up the timer by adding a cleanup function at the end of the useEffect Hook.

import { useState, useEffect } from "react";
import ReactDOM 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>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);
Note: To clear the timer, we needed to give it a name.