← Back

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

useEffect Dependencies and Dynamic API Requests

In Point 8, we learned that an Effect synchronizes a component with something outside React.

Now Pizza Palace needs to show a different pizza when the customer clicks Next pizza. When the pizza ID changes, React must request the new pizza from the server.

The dependency array makes this possible:

useEffect(function loadPizza() {
  // Request the selected pizza.
}, [pizzaId]);

The simple meaning is:

Run after mounting
and run again when pizzaId changes

This lesson focuses on choosing correct dependencies, making new API requests after state changes, and understanding ESLint warnings.


REACT · 01

1. Review the Three Dependency Patterns

There are three common patterns.

No Dependency Array

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

This Effect runs after every committed render.

Empty Dependency Array

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

This Effect runs after mounting. Ordinary state updates do not make it run again.

Dependency Array with a Value

useEffect(function pizzaChanged() {
  console.log("Pizza ID:", pizzaId);
}, [pizzaId]);

This Effect runs after mounting and whenever pizzaId changes.

Syntax When it runs
useEffect(setup) After every committed render
useEffect(setup, []) After mounting
useEffect(setup, [pizzaId]) After mounting and when pizzaId changes

REACT · 02

2. Understand What a Dependency Is

A dependency is a reactive value that the Effect reads.

Reactive values include:

  • component state;
  • component props;
  • variables declared inside the component;
  • functions declared inside the component.

Example:

const [pizzaId, setPizzaId] = useState(1);

useEffect(function showPizzaId() {
  console.log("Selected pizza ID:", pizzaId);
}, [pizzaId]);

The Effect reads pizzaId, so pizzaId belongs in the dependency array.

The setter setPizzaId has a stable identity. It normally does not need to be listed.

Think of the array as a description:

This Effect uses pizzaId.
Therefore, pizzaId is a dependency.

Do not begin by choosing the array. First write the Effect, then list the reactive values that it reads.


REACT · 03

3. See an Effect React to State

This example logs the pizza quantity after mounting and whenever the quantity changes.

import { useEffect, useState } from "react";

export default function PizzaCounter() {
  const [pizzaCount, setPizzaCount] = useState(1);

  useEffect(function pizzaCountChanged() {
    console.log("Pizza count:", pizzaCount);
  }, [pizzaCount]);

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

  return (
    <section>
      <h2>Pizzas: {pizzaCount}</h2>
      <button onClick={handleClick}>
        Add another pizza
      </button>
    </section>
  );
}

Browser Result

Pizzas: 2

Console after one click

Pizza count: 1

Pizza count: 2

The Effect runs after mounting with 1, then runs again because pizzaCount changes to 2.

The button does not call the Effect directly. It changes state. React renders again, compares the dependency, and then runs the Effect because the value changed.


REACT · 04

4. Learn How React Compares Dependencies

React compares every dependency with its value from the previous render.

Conceptually, React asks:

Object.is(previousPizzaId, currentPizzaId);

For strings:

1 compared with 1 → same
1 compared with 2 → different

If every listed dependency is the same, React skips that Effect. If one is different, React synchronizes again.

This is why changing unrelated state does not automatically run every Effect.

const [pizzaId, setPizzaId] = useState(1);
const [customerName, setCustomerName] = useState("");

useEffect(function loadPizza() {
  console.log("Load pizza:", pizzaId);
}, [pizzaId]);

Changing customerName renders the component again, but it does not run this Effect because pizzaId did not change.


REACT · 05

5. Request Another Pizza When pizzaId Changes

We will use only two state values:

pizzaId   → which pizza we want
pizzaName → the name returned by the server

The complete flow is:

Customer clicks Next pizza
          ↓
pizzaId changes
          ↓
The Effect runs again
          ↓
Axios requests one pizza
          ↓
pizzaName displays the response

File: src/components/PizzaViewer.tsx

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

// Describe the data we need from the API.
interface PizzaResponse {
  name: string;
}

export default function PizzaViewer() {
  // Start with pizza number 1.
  const [pizzaId, setPizzaId] = useState(1);

  // Show this text before the server responds.
  const [pizzaName, setPizzaName] = useState("Loading...");

  // Run after mounting and whenever pizzaId changes.
  useEffect(function loadPizza() {
    // Example: /api/pizzas/2
    const requestUrl = "/api/pizzas/" + pizzaId;

    axios
      .get<PizzaResponse>(requestUrl)
      .then(function handleSuccess(response) {
        // Display the name returned by the server.
        setPizzaName(response.data.name);
      });
  }, [pizzaId]);

  // Run when the customer clicks the button.
  function handleNextPizza() {
    setPizzaId(pizzaId + 1);
  }

  return (
    <section>
      <h1>Pizza Palace</h1>
      <p>Pizza ID: {pizzaId}</p>
      <p>Pizza: {pizzaName}</p>

      <button onClick={handleNextPizza}>
        Next pizza
      </button>
    </section>
  );
}

Browser Result

Pizza Palace

Pizza ID: 2

Pizza: Pepperoni

Clicking the button changes pizzaId from 1 to 2. The Effect requests pizza 2 and displays Pepperoni.

The endpoint /api/pizzas/2 represents the Pizza Palace backend returning one pizza.

This learning example intentionally leaves out error handling and request cancellation. Its only purpose is to show how [pizzaId] causes a new request.


REACT · 06

6. Follow One Pizza Change Step by Step

Assume the current pizza ID is 1.

Step 1 — The Customer Clicks Next pizza

setPizzaId(2);

Step 2 — React Renders Again

The new state is:

pizzaId = 2

Step 3 — React Compares the Dependency

previous pizzaId = 1
current pizzaId  = 2

The values are different.

Step 4 — The Effect Runs Again

Axios requests:

GET /api/pizzas/2

Step 5 — The Response Updates pizzaName

setPizzaName(response.data.name);

React displays the new pizza name.

The dependency array does not mean “watch everything.” It says that this synchronization process depends on pizzaId.


REACT · 07

7. Why setPizzaName Does Not Repeat the Request

The Effect updates the pizza name:

setPizzaName(response.data.name);

This update causes another render. However, the Effect depends only on:

[pizzaId]

The pizzaId value did not change when the response arrived.

Response updates pizzaName
          ↓
React renders again
          ↓
pizzaId is still 2
          ↓
React skips the Effect

Calling a state setter does not automatically repeat every Effect. React checks the values listed in that Effect's dependency array.


REACT · 08

8. Fix a Missing Dependency

This Effect reads pizzaId, but the dependency array is empty:

// Incorrect
useEffect(function loadPizzas() {
  axios.get("/api/pizzas/" + pizzaId);
}, []);

The first pizza loads, but changing pizzaId does not request new data. The Effect keeps behaving as if its synchronization does not depend on pizzaId.

The correct dependency is:

// Correct
useEffect(function loadPizzas() {
  axios.get("/api/pizzas/" + pizzaId);
}, [pizzaId]);

Missing dependencies can also make an Effect read an outdated value. This problem is often called a stale value or stale closure.

The beginner rule is:

If an Effect reads a changing value from the component, check whether that value belongs in the dependency array.


REACT · 09

9. Listen to the ESLint Dependency Warning

The React Hooks ESLint rule can report a missing dependency:

React Hook useEffect has a missing dependency: 'pizzaId'.
Either include it or remove the dependency array.

For our menu, the fix is straightforward:

// Before: pizzaId is missing
useEffect(function loadPizzas() {
  console.log(pizzaId);
}, []);

// After: dependencies match the code
useEffect(function loadPizzas() {
  console.log(pizzaId);
}, [pizzaId]);

Do not hide the warning automatically.

If you dislike the dependency list, review the Effect's code:

  1. Does this code need an Effect?
  2. Should it run in an event handler instead?
  3. Does the Effect contain more than one synchronization job?
  4. Can a static value move outside the component?

Changing the code is safer than pretending a real dependency does not exist.


REACT · 10

10. Remove an Unnecessary Dependency

An unnecessary dependency makes an Effect run more often than its job requires.

const [pizzaId, setPizzaId] = useState(1);
const [customerName, setCustomerName] = useState("");

// Unnecessary: this request does not use customerName.
useEffect(function loadPizzas() {
  console.log("Request pizza:", pizzaId);
}, [pizzaId, customerName]);

Typing each letter of the customer's name would run the menu Effect again.

The correct dependency list is:

useEffect(function loadPizzas() {
  console.log("Request pizza:", pizzaId);
}, [pizzaId]);

Do not remove a dependency merely to reduce executions. Remove it only when the Effect truly does not read that value.

Missing dependency     → Effect may use outdated information
Unnecessary dependency → Effect may run too often
Correct dependencies   → Effect stays synchronized

REACT · 11

11. Keep User Actions in Event Handlers

Choosing the next pizza and loading it involve two different steps:

Event handler → changes the pizza ID
Effect        → loads the pizza with that ID

The button event updates state:

function handleNextPizza() {
  setPizzaId(pizzaId + 1);
}

The Effect reacts to the new state:

useEffect(function loadPizza() {
  axios.get("/api/pizzas/" + pizzaId);
}, [pizzaId]);

But placing an order is directly caused by clicking Place order. That POST request normally belongs in the submit handler, not in an Effect that watches an orderSubmitted variable.

Use this question:

Did a specific user action cause the work, or must the component stay synchronized because a value changed?


REACT · 12

12. Summary and Practice

Main Rule

The dependency array describes the reactive values
used by the Effect.
Situation Correct idea
Effect reads pizzaId Include [pizzaId]
Effect reads no reactive value [] may be correct
Effect reads pizzaId but uses [] Dependency is missing
Effect does not read customerName Do not add it
Customer clicks Place order Use an event or submit handler

Pizza Palace Flow

Click Next pizza
    ↓
setPizzaId(2)
    ↓
React renders
    ↓
pizzaId changed
    ↓
Effect requests /api/pizzas/2
    ↓
setPizzaName saves the name
    ↓
React displays Pepperoni

Practice

  1. Log the request URL before Axios sends it.
  2. Explain why [pizzaId] is correct for the request.
  3. Add a customer-name input and confirm that typing does not request a pizza again.
  4. Intentionally remove pizzaId, read the ESLint warning, and then repair it.
  5. After you understand this example, replace the button with a category select.

Important Limitation

This example does not show an error message. Also, if the customer clicks quickly, an older slow response could arrive after a newer response. The next point will add error handling, cleanup, cancellation, and protection from outdated responses.

Next lesson: Effect cleanup and cancelling Axios requests.

← Back↑ Back to the top