Skip to content
wannacode

State

In short: State is a value a component remembers between renders; in React it is kept with useState, and every change renders the component again.

Problem

The button does not count

The product card has an "Add" button and an "In the cart" counter. Press "Add" three times.

React

The console says count = 1, 2, 3. The card says 0. There is no error. The variable count changes, but the screen does not follow it. Where is the difference?

The idea

A plain variable is not enough

The card is not drawn by your code but by the library. It draws again only when it knows that something changed. count++ changes the variable, but tells nobody.

ProductCard is a function. React calls it when it draws the card, and it returns what to show. let count = 0 is a local variable of that call. The click changes it, but nobody calls the function again, so the card stays at 0.

There is a second problem. If React calls the function again for another reason, the first line is let count = 0 again. Everything collected in the variable is lost.

You need data that the component remembers between two renders and whose change draws it again. This is called state.

The idea

useState and ref

const [count, setCount] = useState(0);
function add() {
setCount(count + 1);
}
  • useState(0) returns two things: the value and the function that changes it. The zero is only the initial value.
  • count is the value for this render.
  • setCount(1) remembers the new value and tells React to draw the card again. React calls ProductCard again, and this time useState returns 1.

useState is a hook: a React function that is called at the top of the component, never inside an if or a loop. React tells the states apart by the order in which they are called.

The click handler is given as a function: onClick={add}. If you write onClick={add()}, add is called while the card is being drawn. It changes the state, which draws again, and React stops with the error "Too many re-renders".

React

See it

See what happens on a click

function ProductCard({ product }) {
const [count, setCount] = useState(0);
function addThree() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<article>
<button onClick={addThree}>+3</button>
<p>In the cart: {count}</p>
</article>
);
}
Step by step, in words
  1. The card keeps count in React’s state: useState(0) gives the value and the function that changes it.
  2. React calls the function ProductCard. It does not touch the page: it returns a description, a button and a text with count = 0.
  3. The first time, the description becomes real elements in the DOM. The page now shows “In the cart: 0”.
  4. A click on +3: the browser calls addThree.
  5. setCount(count + 1) three times. In this call count is a snapshot: 0. Each time “become 1” goes into the queue. The screen does not change yet.
  6. The handler is done. React applies the queue: 1, 1, 1 gives 1. Then it calls ProductCard again, and now count is 1.
  7. React compares the new description with the old one. The button is the same, only the text changed. In the DOM only the text changes.
  8. That is why the counter shows 1, not 3. When the new value depends on the old one, pass a function: setCount((c) => c + 1).

The card is not built again. The library compares the new description with the old one and changes only the difference in the DOM: the text. The button is the same element and the focus stays on it. That is the difference from render() in the DOM module, which replaced everything.

Try it

Fix the counter

This is the card from the start. Make count state, so that after every click on "Add" the card shows the new number.

React

The idea

The value is a snapshot

Mineral water is also sold in a pack of 6 bottles. It is handy for the pack button to call add() six times. Here the two libraries behave differently.

count is a constant of one render. setCount does not change count in the handler that is running now. It puts a request in a queue, and React applies it after the handler:

function addBox() {
for (let i = 0; i < 6; i++) {
setCount(count + 1); // 0 + 1, six times: "become 1"
}
}

The six requests are "become 1" and the counter becomes 1, not 6. Say "add 1 to the latest value": pass a function instead of a value.

setCount((c) => c + 1);

React calls each such function with the result of the previous one: 0 → 1 → 2 … → 6. The rule: when the new value depends on the old one, pass a function.

Try it

Find the bug

"Add a pack" should add 6 bottles. Which line is to blame?

Pick the line with the bug.

Try it

A pack of water

Write addBox() so that "Add a pack (6 pcs)" adds 6 bottles to what is already in the cart. The check presses "Add" once, then the pack twice, and expects 1, 7 and 13.

React

The idea

Each card remembers its own

State belongs to the instance, not to the code. The catalogue has two cards from the same ProductCard, and each has its own count:

React

Press "Add" on the bread twice and on the milk once: 2 and 1. The library keeps the state of each card by its place in the tree of components. Remove the card from the page and its state goes with it. When two cards must show the same number, it has to live in their common parent. That is the lesson on shared state in this module.

More precisely, if you are curious

Why a snapshot. React does not change count in place, because the function was called with this value, and everything in it, the handlers too, sees that value. So one render is consistent: whatever is computed from count is computed from one and the same value.

When React does not draw. If setCount gets the same value (compared with Object.is), React skips the render. This matters in the next lesson, for arrays.

Updates are batched. All setCount calls in one handler give one render, and since React 18 also in setTimeout and after await.

StrictMode. In development <StrictMode> calls every component twice, to catch a component that changes something outside while it draws. The sandbox does not use it, and a Vite project has it in main.jsx.

Class components. Older React code writes the component as a class with this.state and this.setState. New code uses functions and hooks.

Exercise

Choose the quantity

The catalogue has three cards. In each one add a quantity that starts at 1, a "−" button (name for the screen reader: "Fewer White bread"), a "+" button ("More White bread") and a line "2 × €1.20 = €2.40" with the quantity, the price and the total. "−" is disabled when the quantity is 1. Each card counts on its own.

React

Test

Check yourself

1. Why do let count = 0 and count++ in the component not change the screen?
2. What does setCount(5) do?
3. count is 0. The handler calls setCount(count + 1) three times. What does the card show?
4. Why is onClick={add()} a mistake?
5. The page has two ProductCards, each with its own count. The customer presses "Add" in the first one. What happens?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·