Skip to content
WANCODE
BM

State: Components That Remember

18 minIntermediateReact

In this lesson

  • Explain why a normal variable can't update the screen
  • Create state with useState and update it with its setter
  • Update arrays and objects by creating new ones instead of mutating
  • Use a functional update when the next value depends on the previous one

The big idea

State is data a component remembers between renders. When you call the setter, React runs your component again with the new value and updates the screen.

Props come from the parent. But some data belongs to the component itself and changes while the user interacts: a counter, a list of orders, whether a menu is open. That's state.

Why a normal variable doesn't work

A component is a function. Every time React renders it, the function runs from the top — so let cups = 0 starts at 0 every render. And changing a local variable doesn't tell React anything, so it never re-renders. The variable changes; the screen doesn't.

Forgets, and React isn't told

JSX
function Counter() {
  let cups = 0;

  function add() {
    cups = cups + 1; // changes, but no re-render
  }

  return <button onClick={add}>{cups}</button>;
}

Remembered, and triggers a re-render

JSX
import { useState } from "react";

function Counter() {
  const [cups, setCups] = useState(0);

  function add() {
    setCups(cups + 1); // new value + re-render
  }

  return <button onClick={add}>{cups}</button>;
}
useState(initial)
A hook — a special React function whose name starts with use. It returns a pair: the current value and a function to change it.
const [cups, setCups]
Array destructuring. Name them whatever you like; the convention is thing and setThing.
Re-render
React calls your component function again. This time useState returns the new value, and React updates only the parts of the DOM that changed.
Click → setter → React re-runs the component → the DOM is updated with the difference.

Rules of hooks

Call useState at the top level of your component — not inside if, loops or nested functions. React tells hooks apart by the order they're called in, so the order must be the same on every render.

Rule 1: never mutate state

React decides whether to re-render by checking if the new value is a different value (it uses Object.is). If you push into the same array and pass that same array back, React sees "nothing changed" and skips the update. Always create a new array or object.

Mutates (no re-render)

JSX
orders.push("Order #3");
setOrders(orders); // same array!

user.name = "Siti";
setUser(user);     // same object!

New value (re-renders)

JSX
setOrders([...orders, "Order #3"]);

setUser({ ...user, name: "Siti" });

setOrders(orders.filter((o) => o !== "Order #1"));

Rule 2: depend on the previous value? Pass a function

Inside one render, cups is a snapshot — a fixed number. Calling the setter doesn't change it; it asks React for a new render. So if you queue several updates from the same snapshot, they all compute from the same old number. Pass an updater function instead and React feeds it the latest value.

Adds 1, not 3 (cups is 0 all along)

JSX
function addThree() {
  setCups(cups + 1); // 0 + 1
  setCups(cups + 1); // 0 + 1
  setCups(cups + 1); // 0 + 1
}

Adds 3

JSX
function addThree() {
  setCups((c) => c + 1); // 0 → 1
  setCups((c) => c + 1); // 1 → 2
  setCups((c) => c + 1); // 2 → 3
}

The same snapshot problem shows up with timers: a setTimeout callback remembers the cups from the render where it was created. You'll fix exactly that in exercise 2.

Click +1 cup and open the Console: the variable goes up, but the big number stays at 0.

Replace let cups = 0 with state: const [cups, setCups] = useState(0). Then update addCup to call setCups(cups + 1). Do the same inside addCupLater for now (setCups(cups + 1) inside the timeout) — you'll look at it again in the next exercise.

Checks

  • The counter starts at 0
  • Clicking "+1 cup" updates the screen

Check understanding

Q1

Why doesn't let count = 0; … count++ update the screen in a component?

Q2

count is 0. A click runs setCount(count + 1); setCount(count + 1);. What's count after the re-render?

Q3

How should you add item to an array in state?

What you learned

  • const [value, setValue] = useState(initial) gives a component memory between renders.
  • Calling the setter re-runs the component with the new value; React then updates the DOM.
  • Never mutate state — make new arrays/objects with spread: [...list, x], { ...obj, key: v }.
  • When the next value depends on the previous one, use setValue((prev) => …).
Preview

Console output from your code appears here.

Step 1 of 3