Command Palette

Search for a command to run...

Hectal
PHASE 4Beginner React 16.8+ ~22 min· topic 2 of 8

Topic 4.2

State: A Component's Memory

In one line

useState gives a component a value that React remembers between renders plus a setter function; calling the setter stores a new value and makes React render the component again so the screen shows it.

Think of it like this

A whiteboard in a classroom. The teacher (React) wipes the room clean every lesson (every render), so anything written on scraps of paper (local variables) is thrown away. But the whiteboard stays: whatever is written there is still there next lesson. State is the whiteboard: React keeps it for your component between renders, and when someone writes something new on it, the class gets a fresh lesson (a re-render) that uses the new value.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

State
Data a component remembers between renders. Changing it makes the component render again.
Hook
A React function whose name starts with use (like useState) that adds a React feature to a component.
useState
The Hook that adds a state variable: const [value, setValue] = useState(initial).
Initial state
The value passed to useState, used only on the first render.
Setter (set function)
The second thing useState returns. Calling it stores a new value and asks React to re-render.
Re-render
React calling your component function again to get an updated description of the screen.
Instance
One rendered copy of a component. <Counter /> twice makes two instances with separate state.
Array destructuring
const [a, b] = array: naming the items of an array in one line.

Step by step

01Start with the broken version

This gallery tries to remember the picture number in a local variable. The click handler changes it, but the screen never moves.

Gallery.jsxwhole filejsx
function Gallery() {
  let index = 0;               // created as 0 on every render
  function handleClick() {
    index = index + 1;         // changes a variable React doesn't watch
  }
  return <button onClick={handleClick}>Picture {index + 1}</button>;
}

02Import useState

useState is a named export of the react package.

Gallery.jsxwhole filejsx
import { useState } from "react";

03Declare a state variable

Replace the local variable with a state variable. index is the current value; setIndex is how you change it. 0 is used only the first time.

Gallery.jsxwhole filejsx
const [index, setIndex] = useState(0);

04Call the setter in the handler

Instead of changing index, ask React to store a new value. React renders Gallery again, useState returns 1, and the button shows Picture 2.

Gallery.jsxwhole filejsx
function Gallery() {
  const [index, setIndex] = useState(0);
  function handleClick() {
    setIndex(index + 1);
  }
  return <button onClick={handleClick}>Picture {index + 1}</button>;
}

05What happens on each click

Your setter call is the trigger. React re-runs the component with the stored value and updates the DOM to match.

What happens on each clickdiagram
Rendering diagram…

06Add more state, and more instances

Add a second variable for a show/hide toggle. Then render two galleries: each keeps its own index and showMore, because state belongs to the instance.

Add more state, and more instancesdiagram
Rendering diagram…

Try it yourself

  1. 1

    Count backwards too

    In the gallery, add a Previous button. Predict: what should happen at the first place? Make it wrap to the last place.

  2. 2

    Add a third counter

    In the last example, add <Counter label="Kiwis" />. Predict its starting number and whether clicking it changes the others.

  3. 3

    Change the initial state

    In the first example, change useState(0) to useState(10). Predict the first line in the console. Then think: if you changed it to useState(count + 100), why would that not work? (count doesn't exist yet when useState is called.)

Code & diagrams

A local variable forgets; state remembers New tab

The handler does change `clicks` to 1, but the next render starts it at 0 again, and the screen shows 0. Only `count`, the state, survives each render.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

render: clicks = 0 | count = 0
handler: clicks is now 1
render: clicks = 0 | count = 1
handler: clicks is now 1
render: clicks = 0 | count = 2
Several state variables in one component New tab

`index` and `showMore` are independent: moving to the next place keeps the details open. Click Next a few more times and watch it wrap around.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Hawa Mahal in Jaipur | Has 953 small windows called jharokhas.
Each instance has its own private state New tab

Same component, two instances, two separate counts. Neither counter can see the other's state, and App can't see either.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Apples: 2
Mangoes: 1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Update the variable instead of calling the setter

In the gallery, change handleNext to index = index + 1; (and declare let [index, setIndex] = useState(0) so it compiles).

terminal
$ Preview
── what you'll see ──
Clicking Next does nothing. No error, no warning.

Break #2

Expect the initial value to reset the state

Pass useState(label.length) in Counter and later change the label prop from the parent.

terminal
$ Preview
── what you'll see ──
The count keeps its old value. The new initial value is ignored.

Myth vs fact

Myth

setCount(5) changes count to 5 right away.

Fact

It asks React for a new render in which count will be 5. The count in the code that's running keeps its old value (Topic 4.4).

Myth

State is shared by every copy of a component.

Fact

Each rendered instance has its own state. To share, lift the state to a common parent (Topic 6.3) or use context (Phase 9).

Myth

Every variable in a component should be state.

Fact

Use state only for data that changes over time and must be shown. Values you can compute from props or other state should be computed during render, not stored (Topic 6.1).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    State lives in the component's fiber (React's internal record for that instance) as a linked list of hook slots, in call order. useState reads the next slot each time it's called. That's the mechanism behind the Rules of Hooks (Topic 4.3) and why state is tied to a position in the tree, not to a variable name.

  • ▸

    Lazy initial state: useState(() => loadDraftFromStorage()) passes a function; React calls it only on the first render. useState(loadDraftFromStorage()) would run the expensive call on every render and throw the result away.

  • ▸

    If you set state to a value that is the same as the current one (compared with Object.is), React skips re-rendering the children. It may still call your component once before bailing out, which is harmless if your component is pure.

  • ▸

    The setter function's identity is stable: it's the same function on every render, so you can safely leave it out of effect and memo dependency lists (Phase 7).

Remember this

  1. 1

    Why a normal variable doesn't work: a component is a function, and React calls it again on every render. A let count = 0 inside it is created fresh, as 0, on every call, so changes are lost. And changing a local variable doesn't tell React anything, so React doesn't render again anyway. You need two things: a value that survives between renders, and a way to ask React to render again. useState gives you both.

  2. 2

    useState is a Hook: a special React function whose name starts with use and that lets a component "hook into" React features. You call it at the top of your component: const [count, setCount] = useState(0);. The argument (0) is the initial state, used only on the very first render. It returns an array of exactly two things, which you name with array destructuring: the current value and a setter function. The convention is [something, setSomething].

  3. 3

    Calling the setter, like setCount(5), does two things: it tells React to store the new value, and it triggers a re-render. React then calls your component again; this time useState(0) ignores the 0 and returns the stored 5. The new JSX shows 5, and React updates the page. The setter does not change the count variable you already have in the running code (Topic 4.4 explains why).

  4. 4

    A component can have as many state variables as it needs, each with its own useState call: const [index, setIndex] = useState(0); and const [showMore, setShowMore] = useState(false);. State can hold any JavaScript value: numbers, strings, booleans, null, objects and arrays (Topics 4.6 and 4.7 show how to update those). Keep unrelated values in separate variables; group values that always change together (Topic 6.1).

  5. 5

    State is private to each instance. Render <Counter /> twice and you get two independent counters: each copy has its own state. The parent can't read or change a child's state; if a parent needs the value, the state moves up into the parent and goes down as props (Topic 6.3, lifting state up). React keeps each piece of state attached to the component's position in the tree, not to the function or a variable name.

  6. 6

    How does React know which state belongs to which useState call? A component doesn't pass any id. React counts: the first useState call in a render gets the first saved value, the second call gets the second, and so on. That is why hooks have rules about where you may call them (Topic 4.3).

Explain it without notes

01

Why can't a component remember a value in a local variable?

02

What does useState return, and what does calling the setter do?

03

If you render a component three times, how many copies of its state exist?

04

When is the argument to useState used?

Practice

01

Build a Toggle component with a button that switches between ON and OFF on each click.

02

Build a NameTag with two buttons, Asha and Ravi, and a heading Hello, <name>! that starts as Hello, guest!.

Trade-offs

  • ↔

    Many small useState calls are simple to read; when several values always change together or the update logic grows, one object or a reducer (Phase 6) keeps them consistent.

  • ↔

    Local state keeps a component self-contained, but state another component needs must be lifted up, which spreads props through the tree (Phase 6 and Phase 9).

Done when you can

  • I can explain why a local variable can't hold a component's memory.

  • I can declare state with const [x, setX] = useState(initial).

  • I can update state with its setter and know that triggers a re-render.

  • I can use several state variables in one component.

  • I know each component instance has its own private state.