When to use useEffect

The useEffect hook in React is used when you want to execute a function after the component has been rendered. This can be either when the component first mounts when it updates, or both.

It's a good place to put code for data fetching, subscriptions, timers, or any side-effects that you want to execute outside the rendering phase of the component.

Examples:

  1. Fetching data from an API when a component first mounts.
  2. Setting up a subscription when a component mounts and cleaning it up when it unmounts.
  3. Running a side effect when a certain prop or state variable changes.

When not to use useEffect

On the flip side, you shouldn't use useEffect when you want to run code synchronously right during the render. For instance, you should not put your regular JSX logic or state updates that you want to happen immediately inside useEffect.

If you want to run code in response to user events like clicks or key presses, you generally don't need useEffect; you can use event handlers instead.

Lifecycle Phases and State

React components have several lifecycle phases: mounting (the component is being created and inserted into the DOM), updating (the component is being re-rendered as a result of changes to either its props or state), and unmounting (the component is being removed from the DOM).

Mounting

In the mounting phase, useState allows you to set the initial state of your component.

In the example below, we invoke our fetch in the useEffect hook with an empty array as a dependency. By using an empty array, it tells our code to run the contents of that useEffect only when the component mounts.

import {useState, useEffect} from "react";
import fetchData from "./api";
import Potatos from "./components/Potatos/Potatos";
function App() {
  const [ potatos, setPotatos] = useState([])
	const [error, setError] = useState("");

function fetchPotatos(){
  fetchData()
  .then((data) => {
    setPotatos(data.potatos);
  })
  .catch((error) => {
    setError(error.message)
  });
}
 
useEffect(() => {
  fetchPotatos()
} , [])

return (
      <main className="App">
        <nav className="nav">
          <img src={logo} className="App-logo" alt="logo" />
          <Form />
        </nav>
        {error && <Error message={error} />}
        {!error && <Potato />
		</main>
)}

Updating

During the updating phase, you can update the state based on user interactions, network responses etc. This is done using the update function returned by useState. When state updates, the component re-renders with the new state. If you had some functionality you want to run every time a piece of state is updated, you can put that functionality inside a useEffect and include that piece of state in the dependency array. To avoid an infinite loop, make sure that functionality isn’t actually updating that same piece of state thats in the dependency array (because any time that updates it will trigger the code in the useEffect again).

import React, { useState, useEffect } from 'react';

function ClickCounter() {
    const [count, setCount] = useState(0);

    // This useEffect will run after the initial render and every time `count` changes
    useEffect(() => {
        // Update the document title using the browser API
        document.title = `You clicked ${count} times`;
    }, [count]);  // Only re-run the effect if count changes

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    );
}

export default ClickCounter;

Unmounting