← Back

React.js + Next.js · Module 3. Forms and Effects · Lesson 8 · Point 8

Side Effects and the React useEffect Hook

React can display information from props and state. Sometimes a component must also communicate with something outside React.

For Pizza Palace, the menu component may need to ask the Pizza Palace API which pizzas are currently available.

That request is a side effect. React gives us the useEffect() Hook for work that must happen because a component appeared or because one of its dependencies changed.

We will build the idea in this order:

Render the component
        ↓
Show the interface
        ↓
Run the Effect
        ↓
Receive external data
        ↓
Update state and render again

This lesson uses regular functions and simple Pizza Palace examples.


REACT · 01

1. Understand Mounting, Updating, and Unmounting

A React component has three important lifecycle moments.

Moment Simple meaning Pizza Palace example
Mounting The component appears on the page The pizza menu opens
Updating The component renders again The selected category changes
Unmounting The component is removed The pizza menu closes

Here is a small example:

import { useState } from "react";

function PizzaMenu() {
  return <h2>Pizza menu is open</h2>;
}

export default function App() {
  const [showMenu, setShowMenu] = useState(true);

  function handleClick() {
    setShowMenu(!showMenu);
  }

  return (
    <main>
      <button onClick={handleClick}>
        Show / hide menu
      </button>

      {showMenu && <PizzaMenu />}
    </main>
  );
}

Browser Result

Pizza menu is open

When the message appears, PizzaMenu mounts. Hiding it unmounts it.

When showMenu changes from true to false, React removes PizzaMenu from the page. When it becomes true again, React mounts a new instance.


REACT · 02

2. A State Update Can Cause a Re-render

State lets a component remember information.

const [pizzaCount, setPizzaCount] = useState(0);
  • pizzaCount is the current value;
  • setPizzaCount updates the value;
  • 0 is the initial value.

Use the state setter inside an event handler:

import { useState } from "react";

export default function CartCounter() {
  const [pizzaCount, setPizzaCount] = useState(0);

  console.log("CartCounter rendered");

  function handleAddPizza() {
    setPizzaCount(pizzaCount + 1);
  }

  return (
    <section>
      <h2>Pizzas in cart: {pizzaCount}</h2>
      <button onClick={handleAddPizza}>
        Add one pizza
      </button>
    </section>
  );
}

Browser Result

Pizzas in cart: 1

Clicking the button updates state. React calls the component again and displays the new number.

The sequence is:

Customer clicks
      ↓
handleAddPizza runs
      ↓
setPizzaCount updates state
      ↓
React renders CartCounter again

A re-render is not a new mount. The component remains on the page, and useState remembers the current value.

React also does not replace the entire DOM. It updates only the parts that need to change.


REACT · 03

3. Keep Rendering Code Pure

Rendering code runs when React calls the component function. It should calculate and return the interface.

This calculation is safe during rendering:

const pizzaPrice = 12;
const quantity = 2;
const total = pizzaPrice * quantity;

This is incorrect:

import { useState } from "react";

export default function BrokenCounter() {
  const [count, setCount] = useState(0);

  // Wrong: this updates state during rendering.
  setCount(count + 1);

  return <h1>Count: {count}</h1>;
}

Why does it fail?

Render
  ↓
Update state
  ↓
Render again
  ↓
Update state again
  ↓
Too many re-renders

Put an update caused by a click inside an event handler. Put synchronization with an external system inside an Effect.


REACT · 04

4. Choose Between Rendering, an Event, and an Effect

These three places have different jobs.

Place When it runs Pizza Palace example
Rendering code When React renders Calculate the cart total
Event handler After a specific user action Submit an order after a click
Effect Because the component appeared or a dependency changed Load the current pizza menu

Normal Rendering Calculation

const total = price * quantity;

No server or browser API is involved, so we do not need useEffect().

Event Handler

function handleOrder() {
  console.log("Customer placed the order");
}

The customer clicking a button directly causes this work. It belongs in an event handler.

Effect

useEffect(function loadMenu() {
  console.log("Ask the Pizza Palace API for the menu");
}, []);

No button click is required. The menu request happens because the menu component appeared.

The most important rule is:

If there is no external system, you probably do not need an Effect.


REACT · 05

5. Do Not Send an HTTP Request During Rendering

This code is incorrect:

import { useState } from "react";
import axios from "axios";

interface Pizza {
  id: number;
  name: string;
}

export default function PizzaMenu() {
  const [pizzas, setPizzas] = useState<Pizza[]>([]);

  // Wrong: this request runs during every render.
  axios
    .get<Pizza[]>("/api/pizzas")
    .then(function savePizzas(response) {
      setPizzas(response.data);
    });

  return <p>Found pizzas: {pizzas.length}</p>;
}

The problem is the location of the request, not Axios.

PizzaMenu renders
       ↓
Axios sends a request
       ↓
The response updates state
       ↓
PizzaMenu renders again
       ↓
Axios sends another request

This may create repeated requests, unnecessary server traffic, and unpredictable behavior.

The request must be moved out of the rendering calculation.


REACT · 06

6. Write Your First useEffect

First import useEffect:

import { useEffect } from "react";

Then call it at the top level of the component:

useEffect(function showMessage() {
  console.log("The Pizza Palace menu appeared");
}, []);

There are two important parts:

function showMessage() { ... }  → Effect setup function
[]                              → Dependency array

Complete example:

import { useEffect } from "react";

export default function PizzaMenu() {
  console.log("1. PizzaMenu rendered");

  useEffect(function showMessage() {
    console.log("2. Effect ran");
  }, []);

  return <h1>Pizza Palace menu</h1>;
}

Browser Result

Pizza Palace menu

Console

1. PizzaMenu rendered

2. Effect ran

React renders the heading first. The Effect runs after React commits the interface.

Call useEffect() before return. Do not call it inside a condition, loop, event handler, or .map() callback.


REACT · 07

7. Understand the Execution Order

Look at the letters in this example:

import { useEffect } from "react";

export default function App() {
  console.log("A");

  useEffect(function runEffect() {
    console.log("B");
  }, []);

  console.log("C");

  return <h1>Pizza Palace</h1>;
}

The usual order is:

A
C
B

Step by step:

  1. React calls App().
  2. JavaScript prints A.
  3. React registers the Effect but does not run its setup yet.
  4. JavaScript prints C.
  5. React commits the interface.
  6. React runs the Effect and prints B.

Think of useEffect() as telling React:

After this interface is committed, synchronize it with the external system.


REACT · 08

8. Understand the Dependency Array

The dependency array tells React when the Effect must synchronize again.

No Dependency Array

useEffect(function runAfterEveryCommit() {
  console.log("Effect ran");
});

This runs after every committed render.

Empty Dependency Array

useEffect(function runAfterMounting() {
  console.log("Effect ran");
}, []);

This runs after the component mounts. Ordinary re-renders do not run it again.

Dependency Inside the Array

useEffect(function categoryChanged() {
  console.log("Category:", category);
}, [category]);

This runs after mounting and again when category changes.

Syntax When the Effect runs
useEffect(setup) After every committed render
useEffect(setup, []) After mounting
useEffect(setup, [category]) After mounting and when category changes

You cannot freely hide values from this array. Every reactive value used by the Effect must be included as a dependency.


REACT · 09

9. Load the Pizza Menu Correctly

Now move the Axios request into an Effect.

The Pizza Palace architecture is:

React PizzaMenu
      ↓ GET /api/pizzas
Pizza Palace API
      ↓
Pizza database

File: src/components/PizzaMenu.tsx

import { useEffect, useState } from "react";
import axios from "axios";

interface Pizza {
  id: number;
  name: string;
  price: number;
}

export default function PizzaMenu() {
  const [pizzas, setPizzas] = useState<Pizza[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(function loadPizzaMenu() {
    axios
      .get<Pizza[]>("/api/pizzas")
      .then(function handleSuccess(response) {
        setPizzas(response.data);
      })
      .catch(function handleError() {
        setError("Could not load the pizza menu.");
      })
      .finally(function finishRequest() {
        setIsLoading(false);
      });
  }, []);

  if (isLoading) {
    return <p>Loading pizza menu...</p>;
  }

  if (error) {
    return <p>{error}</p>;
  }

  function renderPizza(pizza: Pizza) {
    return (
      <li key={pizza.id}>
        {pizza.name} — €{pizza.price}
      </li>
    );
  }

  return (
    <section>
      <h1>Pizza Palace menu</h1>
      <ul>{pizzas.map(renderPizza)}</ul>
    </section>
  );
}

Browser Result

Pizza Palace menu
  • Margherita — €10
  • Pepperoni — €12
  • Vegetariana — €11
The component first shows a loading message. After the API responds, state updates and the pizza list appears.

The endpoint /api/pizzas represents the Pizza Palace backend. The React application does not connect directly to the database.


REACT · 10

10. Follow the Menu Request Step by Step

Here is the complete journey:

Step 1 — Initial Render

pizzas = []
isLoading = true
error = ""

The customer sees:

Loading pizza menu...

Step 2 — The Effect Runs

Axios sends:

GET /api/pizzas

Step 3 — The Server Responds

handleSuccess() stores the array in state:

setPizzas(response.data);

Step 4 — The Request Finishes

setIsLoading(false);

Step 5 — React Renders Again

The loading message disappears and the menu appears.

The Effect does not repeat after that state update because its dependency array is empty:

}, []);

If the request fails, handleError() stores a friendly message and the component displays it instead of the menu.


REACT · 11

11. Understand Strict Mode and Future Cleanup

During development, React Strict Mode may run one extra setup-and-cleanup cycle for an Effect. React does this to reveal missing cleanup logic.

You may therefore notice two development requests even with:

useEffect(function loadPizzaMenu() {
  // Request code
}, []);

Remember:

  • [] means the Effect belongs to each mount;
  • unmounting and mounting again runs it again;
  • Strict Mode performs an additional development check;
  • production behavior does not include that extra development-only check.

Some Effects need cleanup:

connect      → disconnect
start timer  → stop timer
add listener → remove listener
start request → cancel it or ignore an outdated result

Cleanup and request cancellation will be studied in a later point. For now, do not remove Strict Mode just to hide repeated development behavior.


REACT · 12

12. Summary and Practice

Main Rules

Rendering calculates the interface.
Events respond to specific user actions.
Effects synchronize with external systems.
Concept Meaning
Mount A component appears
Re-render React calls the component again
Unmount A component disappears
Side effect Work involving something outside rendering
useEffect() Synchronize after React commits a render
Dependency array Describes when synchronization must happen again

Do Not Use an Effect For

  • multiplying a price by a quantity;
  • filtering an array for display;
  • responding directly to a button click;
  • creating values that can be calculated during rendering.

Use an Effect For

  • loading the Pizza Palace menu when a client-side component appears;
  • connecting to a live order-status service;
  • synchronizing with a browser API;
  • starting something that needs later cleanup.

Practice

  1. Add console.log() during rendering and inside an Effect. Compare their order.
  2. Build a component that loads /api/pizzas after mounting.
  3. Add loading, error, and success interfaces.
  4. Add a category state and explain why an Effect that uses it needs [category].
  5. Explain why placing the Axios request directly in the component body is dangerous.

Next lesson: Effect cleanup, request cancellation, and asynchronous Effect patterns.

← Back↑ Back to the top