State: a component's memory
Why a normal variable can't update the screen, and how useState gives a component memory: a value and its set function, several values, and state per copy.
- 01Explain why a local variable failsSay the two reasons a changed
letvariable never reaches the screen: it starts again at every render, and changing it doesn't ask React to render. - 02Add state with useStateUse the value and set function
useStatereturns, give a component several state variables, and pick what belongs in state. - 03Predict whose state changesTell that every copy of a component keeps its own state, and explain how React matches each
useStatecall to its value by the order of the calls.
A quantity that won't go up
A shop's tea card has a quantity and a button that adds one. Here is a first try, written with what you know so far: a variable that holds the quantity, and a click handler that changes it. Click Add one three times.
export default function TeaCard() { let quantity = 1; function addOne() { quantity = quantity + 1; console.log("quantity is now", quantity); } return ( <div> <h3>Green tea</h3> <p>Quantity: {quantity}</p> <button onClick={addOne}>Add one</button> </div> );}Line 2 makes a variable, quantity, set to 1. addOne on lines 4 to 7 adds one to it on line 5 and prints the new value on line 6. Line 12 shows the quantity, and line 13 runs addOne on every click.
The console under the result says quantity is now 2, 3, 4: the variable really does change. The screen still says Quantity: 1. The click changed a JavaScript variable, and React drew the screen once, before any click. Nothing told it to draw again.
A component needs a way to remember a value between clicks and to show the new value when it changes. React calls that memory state. Here is the same card with a state variable:
import { useState } from "react"; export default function TeaCard() { const [quantity, setQuantity] = useState(1); function addOne() { setQuantity(quantity + 1); } return ( <div> <h3>Green tea</h3> <p>Quantity: {quantity}</p> <button onClick={addOne}>Add one</button> </div> );}Line 1 imports useState from React. Line 4 asks React to remember a quantity for this card, starting at 1, and gets back two things: quantity, the value now, and setQuantity, a function that changes it. Line 7 calls setQuantity(quantity + 1) instead of changing the variable. Click Add one: the screen says 2, then 3. Every call to setQuantity stores the new value and asks React to run TeaCard again, and this time line 4 hands back the new value.
At a match, a player can count the score in their head all day, and the scoreboard won't change. Only the operator changes the board. When someone tells the operator the new score, the operator writes it down and puts it up for everyone.
- In real life: A player counts the score in their head
- In React: Your code changes a local variable, like
quantity = quantity + 1 - In real life: The scoreboard doesn't move
- In React: The screen keeps what the last render showed
- In real life: The operator writes the score in a notebook
- In React: React keeps the state value between renders
- In real life: A player tells the operator the new score
- In React: Your code calls
setQuantity(2) - In real life: The operator puts the new score on the board
- In React: React renders again and the screen shows 2
State is a component's memory. useState gives you a value that React keeps between renders, and a set function. Calling the set function stores a new value and asks React to render the component again, so the screen shows it.
Why a normal variable isn't enough
Two things stop a local variable, one declared inside the component, from working:
- Changing it doesn't ask React to render. React draws the screen when something asks it to. A plain
=is ordinary JavaScript: React never hears about it. - Every render starts it again. When React does render, it calls your component function from the top. Line 2's
let quantity = 1runs again and makes a new variable, set to 1. Whatever the clicks did is gone.
The first problem is easy to see. The second needs a render that something else starts. This card keeps its price in state, so Apply sale makes React render it. Click Add one three times, then Apply sale, then Add one again, and watch the console.
import { useState } from "react"; export default function TeaCard() { let quantity = 1; const [price, setPrice] = useState(4); console.log("Render: quantity starts at", quantity); function addOne() { quantity = quantity + 1; console.log("quantity is now", quantity); } return ( <div> <h3>Green tea: ${price}</h3> <p>Quantity: {quantity}</p> <button onClick={addOne}>Add one</button> <button onClick={() => setPrice(3)}>Apply sale</button> </div> );}Line 4 is the local variable and line 5 a piece of state, the price, starting at 4. Line 6 prints a line every time TeaCard runs. When the result first appears, the console shows Render: quantity starts at 1 with a small 2 beside it: in development, React's StrictMode calls each component twice to catch mistakes, and this page's console shows a line printed twice in a row once, with the count beside it. Keeping components pure explains why.
The three clicks print quantity is now 2, 3, and 4, and the screen stays at 1. Apply sale calls setPrice(3) on line 18, so React renders: the heading on line 15 says Green tea: $3, and the console prints Render: quantity starts at 1 again. The new render made a new quantity. Click Add one once more: the console says quantity is now 2. The count of 4 was lost.
quantity to 4, but nothing asked React to render, so the page kept saying 1. Apply sale made React call TeaCard again, and line 4 made a new quantity, set to 1.So state has two jobs: keep a value between renders, and ask for a render when it changes. useState gives you one tool for each. Here are the two kinds of variable, side by side:
How to read the table: each row asks one question of a local variable and of a state variable.
| Question | A local variable (let) | A state variable (useState) |
|---|---|---|
| Does it keep its value between renders? | No. Each render runs the function from the top and makes the variable again. | Yes. React keeps the value and hands it back on the next render. |
| Does changing it update the page? | No. React never hears about the change. | Yes. The set function asks React for a new render. |
| How do you change it? | With =, like quantity = quantity + 1. | With its set function, like setQuantity(quantity + 1). |
| Who can see it? | Only the code in that one render. | This copy of the component. Pass it down as a prop to show it somewhere else. |
| What is it good for? | Values you work out during a render, like a line total. | Values that must last between clicks, like a picked quantity. |
The playground runs a card that has both, side by side. Its list shows every click on the local button and every render React makes, with the values each one saw. Press Add one to the local variable twice, then Add one with setQuantity.
function TeaCard() { let localQuantity = 1; const [quantity, setQuantity] = useState(1); function addLocal() { localQuantity = localQuantity + 1; } function addState() { setQuantity(quantity + 1); } return ( <div> <p>Local variable: {localQuantity}</p> <p>State: {quantity}</p> <button onClick={addLocal}>Add one to the local variable</button> <button onClick={addState}>Add one with setQuantity</button> </div> );}0 rendersLocal variable: 1
State: 1
Use the two buttons in the card.
Press Add one to the local variable a few times, then Add one with setQuantity. Each row is a click or a render.
The local clicks add rows but no render: localQuantity becomes 2, then 3, and the card still says Local variable: 1. The setQuantity click adds a render row: React ran TeaCard again, localQuantity started at 1 again, and useState(1) gave 2, the value it remembered. Local variables are still useful. They hold what you work out during one render, like a line total; they just can't remember anything for the next one.
useState: a value React keeps, and a function to change it
Here is the line that gives the card its memory, from the state card above:
const [quantity, setQuantity] = useState(1);Read it from the right:
useState(1)tells React: remember a value for this component, and start it at 1. The 1 is the initial value; React uses it on the first render only.- It returns an array of exactly two items: the value now, and a set function that changes it.
- The square brackets on the left are array destructuring: they take the two items out and name them
quantityandsetQuantity.
useState(1) returns an array of two items: the value React keeps for this component, and a function that changes it. Array destructuring gives them the names quantity and setQuantity.The names are yours to choose. The habit is [something, setSomething]: [price, setPrice], [isOpen, setIsOpen]. Following it makes any React code easier to read, because the set function says which value it changes.
useState is a hook. Hooks are React functions whose names start with use, and they let a component use a React feature: here, memory. Call them at the top level of your component, not inside an if, a loop, or a click handler. The last section of this lesson shows why, and The rules of hooks covers the rules in full.
What happens on each render? This card prints what useState gave it, every time React calls it. Click Add one twice.
import { useState } from "react"; export default function TeaCard() { const [quantity, setQuantity] = useState(1); console.log("Render: useState gave", quantity); return ( <div> <h3>Green tea</h3> <p>Quantity: {quantity}</p> <button onClick={() => setQuantity(quantity + 1)}>Add one</button> </div> );}The console shows Render: useState gave 1, then 2, then 3, each with the StrictMode 2 beside it. Line 4 is the same useState(1) every time, but only the first render gets 1:
- First render. React has nothing stored for this card yet, so it stores the initial value, 1, and line 4 gets
[1, setQuantity]. - A click. Line 11 calls
setQuantity(quantity + 1), which issetQuantity(2)in this render. React stores 2 and asks for a new render. - Second render. React calls
TeaCardagain. Line 4 callsuseState(1), and React hands back what it stored:[2, setQuantity]. The 1 is ignored now.
One thing to notice: setQuantity(2) doesn't change the quantity variable in the code that's running. That render keeps its 1; the next render gets 2. State as a snapshot shows what that means for your handlers, and the next lesson, Render and commit, follows what React does after you call a set function.
More than one piece of state
A component can call useState as many times as it needs, once for each thing to remember, of any type. This tea gallery remembers which tea it shows, a number, and whether the details are open, a boolean (true or false). The teas are in a second file, teas.js, in the next tab. Click Next tea and Show details.
import { useState } from "react";import { teas } from "./teas.js"; export default function TeaGallery() { const [index, setIndex] = useState(0); const [showDetails, setShowDetails] = useState(false); const tea = teas[index]; const isLast = index === teas.length - 1; return ( <div> <h3>{tea.name}: ${tea.price}</h3> <p>Tea {index + 1} of {teas.length}</p> <button onClick={() => setIndex(index + 1)} disabled={isLast}> Next tea </button> <button onClick={() => setShowDetails(!showDetails)}> {showDetails ? "Hide details" : "Show details"} </button> {showDetails && <p>{tea.details}</p>} </div> );}Line 5 keeps index, the position of the tea in the list, starting at 0. Line 6 keeps showDetails, starting at false. Line 7 picks the tea from the list, and line 8 works out whether it's the last one. Both are plain variables, worked out again from state on every render. Line 14's button adds one to index, and is turned off on the last tea. Line 17's button flips showDetails with !, and line 20 shows the details only while it's true (the Conditional rendering lesson covers &&).
The two pieces of state don't affect each other. Open the details, then press Next tea: the details stay open and show the Black tea's text, because showDetails is still true. Each call to a set function changes only its own value.
Keep values in separate state variables when they change separately, like index and showDetails. When two values always change together, one state variable that holds an object can be simpler. Choosing the state structure shows how to choose the shape of your state.
Each copy of a component has its own state
State belongs to one use of a component on the page, not to the function. Here App uses TeaCard twice. TeaCard is in its own file, in the second tab. Click Green tea's Add one twice.
import TeaCard from "./TeaCard.jsx"; export default function App() { return ( <div className="shelf"> <TeaCard name="Green tea" /> <TeaCard name="Mint tea" /> </div> );}In TeaCard.jsx, line 4 keeps a quantity, and line 10's button adds one. In App.jsx, lines 6 and 7 use TeaCard twice. After your clicks, Green tea says Quantity: 3 and Mint tea still says Quantity: 1. Both copies run the same useState(1) line, but React keeps a separate value for each copy.
App uses TeaCard twice, and React keeps a separate quantity for each copy. Two clicks on Green tea made its quantity 3; Mint tea still has 1.Press Edit & run and add a third card under line 7: <TeaCard name="Black tea" />. It appears with Quantity: 1, its own memory from the start.
State is also private. App can't read a card's quantity or change it; only the card's own setQuantity can. That's what makes a component safe to reuse: putting a third card on the page can't break the other two. When two components need the same value, like a cart count that every card adds to, you keep that value in their shared parent and pass it down as props. The practical section below does that, and Sharing state between components covers it in full.
How React knows which state is which
Look at the gallery again: line 5 calls useState(0) and line 6 calls useState(false). Neither call says which value it wants. React doesn't read your variable names either. So how does the second call get showDetails and not index?
By order. For every copy of every component, React keeps its state in a list. On each render, the first useState call gets the first value in the list, the second call gets the second, and so on. Here is a small model of that idea in plain JavaScript, written for this lesson. It isn't React's code, but it works the same way for one card:
const slots = [];
let next = 0;
function useState(initial) {
const index = next;
if (index === slots.length) slots.push(initial);
next = next + 1;
const setState = (value) => { slots[index] = value; };
return [slots[index], setState];
}
function TeaCard() {
const [quantity, setQuantity] = useState(1);
const [showDetails] = useState(false);
return { quantity, showDetails, setQuantity };
}
function render() {
next = 0;
return TeaCard();
}
const first = render();
first.setQuantity(2);
const second = render();
console.log(second.quantity, second.showDetails);Line 1 is the model's memory, slots, and line 2 is next, the slot the next call gets. In useState (lines 4 to 10), line 5 takes the slot number, line 6 stores the starting value only if that slot is new, line 7 moves the counter on, line 8 makes a set function that writes to that one slot (it remembers index, a closure), and line 9 returns the pair. TeaCard on lines 12 to 16 calls it twice, like the gallery. render on lines 18 to 21 puts the counter back to 0, then calls the component. Lines 23 to 26 render, set the quantity to 2, render again, and print 2 false. Press Edit & run to see it, or step through it:
A replay of a small model of useState, written for this lesson in plain JavaScript. It isn't React's own code: React keeps a list like this for every copy of every component.
script
let next = 0; function useState(initial) { const index = next; if (index === slots.length) slots.push(initial); next = next + 1; const setState = (value) => { slots[index] = value; }; return [slots[index], setState];} function TeaCard() { const [quantity, setQuantity] = useState(1); const [showDetails] = useState(false); return { quantity, showDetails, setQuantity };} function render() { next = 0; return TeaCard();} const first = render();first.setQuantity(2);const second = render();console.log(second.quantity, second.showDetails);useState call to a slot by its place in the order. In render 2, the first call finds slot 0, which holds 2 since the click, and the second call finds slot 1, which still holds false.This only works if the calls come in the same order on every render. Put a useState inside an if, and on a render where the if is false, every later call would take its neighbour's slot. That's why hooks go at the top level of a component. React warns you when the order changes, and a linter plugin catches it as you type; The rules of hooks shows both. Real React keeps a linked list of hooks on an object for each copy of each component, which is how two cards get two lists; How hooks work inside reads that code.
Choosing what goes in state on a shop page
A real page has state in more than one place. In this shop, each card remembers its own quantity and whether you've added it, and the page remembers how many items are in the cart. Pick a quantity on each card and press Add to cart.
import { useState } from "react";import TeaCard from "./TeaCard.jsx"; export default function App() { const [cartCount, setCartCount] = useState(0); function addToCart(quantity) { setCartCount(cartCount + quantity); } return ( <> <p>Cart: {cartCount} items</p> <div className="shelf"> <TeaCard name="Green tea" price={4} onAdd={addToCart} /> <TeaCard name="Mint tea" price={5} onAdd={addToCart} /> </div> </> );}In TeaCard.jsx, line 4 keeps the quantity and line 5 keeps added, starting at false. The minus button on lines 16 to 18 is off at 1, so the quantity can't go below 1. Line 24 works out the line total during the render: it isn't state, because price and quantity already say what it is. handleAdd on lines 7 to 10 tells the page how many to add, with onAdd(quantity), then sets added, so line 25's button says Add again.
In App.jsx, line 5 keeps cartCount. The cart belongs to the whole page, so it lives in App, which passes addToCart to both cards on lines 15 and 16. Set Green tea to 2 and add it, then add one Mint tea: the cart says Cart: 3 items. Each card's quantity stays private, and the shared count lives in their parent.
Before you add state, ask two questions. Does it change while the page is open? Can you work it out from props or other state? Only something that changes and can't be worked out needs state. Sort these:
- The quantity a shopper picked on a tea card
- A tea's name, passed in as a prop
- The line total: price times quantity
- Whether a card's details are open
- The list of teas, which never changes
- The text typed in a search box
- Which tea a gallery is showing
- How many items the page's cart holds
Sort each value on a tea shop page: does the component need to remember it in state?
Common mix-ups
- "Changing a variable updates the screen." No. Only a set function asks React to render. A local
letchanges, and the screen keeps the last render's value. - "
useState(1)sets the value to 1 on every render." No. The 1 counts on the first render only. After that,useStatehands back what React kept. - "Two copies of a component share their state." No. Each copy on the page has its own. Clicking Green tea never changes Mint tea.
- "A parent can read or change its child's state." No. State is private to the component that declares it. To share a value, keep it in the parent and pass it down as a prop.
- "Everything that changes on the screen needs its own state." No. A line total that you can work out from
priceandquantityis a plain variable in the render. - "React finds my state by the variable name." No. It goes by the order of the
useStatecalls, which is why they must run in the same order every render.
Practice exercises
Predict first, then check. Each exercise shows its result, or shows it when you press Edit & run; change the code and the result updates as you type.
You click Like three times. What does the page show? Answer first, then press Edit & run to check.
export default function LikeButton() {
let likes = 0;
function like() {
likes = likes + 1;
console.log("likes is now", likes);
}
return (
<p>
Likes: {likes} <button onClick={like}>Like</button>
</p>
);
}export default function LikeButton() { let likes = 0; function like() { likes = likes + 1; console.log("likes is now", likes); } return ( <p> Likes: {likes} <button onClick={like}>Like</button> </p> );}like changes a local variable, which never asks React to render, so the page keeps Likes: 0 from the first render. The console prints likes is now 1, 2, and 3.
Make the button work with state. What does the page show after three clicks?
export default function LikeButton() { let likes = 0; function like() { likes = likes + 1; console.log("likes is now", likes); } return ( <p> Likes: {likes} <button onClick={like}>Like</button> </p> );}import { useState } from "react"; export default function LikeButton() { const [likes, setLikes] = useState(0); function like() { setLikes(likes + 1); } return ( <p> Likes: {likes} <button onClick={like}>Like</button> </p> );}Line 4 keeps likes in state, starting at 0, and line 7 asks React for a new render with one more. After three clicks the page shows Likes: 3.
You click Green tea's Add one twice and Mint tea's once. What does Mint tea's quantity say?
import TeaCard from "./TeaCard.jsx";
export default function App() {
return (
<div className="shelf">
<TeaCard name="Green tea" />
<TeaCard name="Mint tea" />
</div>
);
}import TeaCard from "./TeaCard.jsx"; export default function App() { return ( <div className="shelf"> <TeaCard name="Green tea" /> <TeaCard name="Mint tea" /> </div> );}Each copy keeps its own quantity. Green tea's two clicks make Green tea 3; Mint tea's one click makes Mint tea 2.
Give the card a button that turns gift wrap on and off, labelled Gift wrap: off or Gift wrap: on. What does it say after one click?
import { useState } from "react"; export default function TeaCard() { const [quantity, setQuantity] = useState(1); function addOne() { setQuantity(quantity + 1); } return ( <div> <h3>Green tea</h3> <p>Quantity: {quantity}</p> <button onClick={addOne}>Add one</button> </div> );}import { useState } from "react"; export default function TeaCard() { const [quantity, setQuantity] = useState(1); const [giftWrap, setGiftWrap] = useState(false); function addOne() { setQuantity(quantity + 1); } return ( <div> <h3>Green tea</h3> <p>Quantity: {quantity}</p> <button onClick={addOne}>Add one</button> <button onClick={() => setGiftWrap(!giftWrap)}> Gift wrap: {giftWrap ? "on" : "off"} </button> </div> );}Line 5 adds a second piece of state, starting at false. Lines 16 to 18 flip it on each click, and the label reads it. It changes without touching quantity: the button says Gift wrap: on after one click.
Make Next tea go back to the first tea after the last one, instead of turning off. Which tea shows after three clicks? (The Arithmetic lesson covers %.)
import { useState } from "react";import { teas } from "./teas.js"; export default function TeaGallery() { const [index, setIndex] = useState(0); const [showDetails, setShowDetails] = useState(false); const tea = teas[index]; const isLast = index === teas.length - 1; return ( <div> <h3>{tea.name}: ${tea.price}</h3> <p>Tea {index + 1} of {teas.length}</p> <button onClick={() => setIndex(index + 1)} disabled={isLast}> Next tea </button> <button onClick={() => setShowDetails(!showDetails)}> {showDetails ? "Hide details" : "Show details"} </button> {showDetails && <p>{tea.details}</p>} </div> );}import { useState } from "react";import { teas } from "./teas.js"; export default function TeaGallery() { const [index, setIndex] = useState(0); const [showDetails, setShowDetails] = useState(false); const tea = teas[index]; return ( <div> <h3>{tea.name}: ${tea.price}</h3> <p>Tea {index + 1} of {teas.length}</p> <button onClick={() => setIndex((index + 1) % teas.length)}> Next tea </button> <button onClick={() => setShowDetails(!showDetails)}> {showDetails ? "Hide details" : "Show details"} </button> {showDetails && <p>{tea.details}</p>} </div> );}The % operator gives the remainder, so after the last tea, index 2, the next index is 3 % 3, which is 0. Three clicks go Black tea, Mint tea, then back to Green tea. isLast isn't needed any more.
The card takes its starting quantity from a prop. You click Bulk order, which changes start to 10. What does the card's quantity say?
import { useState } from "react";
function TeaCard({ start }) {
const [quantity, setQuantity] = useState(start);
return (
<p>
Quantity: {quantity}{" "}
<button onClick={() => setQuantity(quantity + 1)}>Add one</button>
</p>
);
}
export default function App() {
const [start, setStart] = useState(1);
return (
<>
<button onClick={() => setStart(10)}>Bulk order</button>
<p>Starting value: {start}</p>
<TeaCard start={start} />
</>
);
}import { useState } from "react"; function TeaCard({ start }) { const [quantity, setQuantity] = useState(start); return ( <p> Quantity: {quantity}{" "} <button onClick={() => setQuantity(quantity + 1)}>Add one</button> </p> );} export default function App() { const [start, setStart] = useState(1); return ( <> <button onClick={() => setStart(10)}>Bulk order</button> <p>Starting value: {start}</p> <TeaCard key={start} start={start} /> </> );}Bulk order makes App render with start 10, and the page says Starting value: 10. But the card's useState(start) already has a value from its first render, so it hands back 1 and ignores 10. The lesson Preserving and resetting state explains the fix, used here on line 21: a key that changes makes React treat the card as a new copy, with new state, so it starts at 10.
Check your understanding
For each code question, ask what is in state, and what asks React to render. Press Edit & run on a question's code to check your answer.
Question 1 of 7Why doesn't
quantity = quantity + 1on aletvariable change the screen?Choose an answer to see the explanation.
Question 2 of 7You click this button once. What does it say?
Read the code, then predictimport { useState } from "react"; export default function Stepper() { const [count, setCount] = useState(5); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>; }Choose an answer to see the explanation.
Question 3 of 7What does
useState(1)return?Choose an answer to see the explanation.
Question 4 of 7You click the first button twice. What does the second button show?
Read the code, then predictimport { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } export default function App() { return ( <> <Counter /> <Counter /> </> ); }Choose an answer to see the explanation.
Question 5 of 7On the second render, React calls
useState(1)again. What does that call return?Choose an answer to see the explanation.
Question 6 of 7
useStatedoesn't take a name. How does React know which state a call gets?Choose an answer to see the explanation.
Question 7 of 7You click Like twice. What does the console print on the second click?
Read the code, then predictexport default function LikeButton() { let likes = 0; function like() { likes = likes + 1; console.log("likes is now", likes); } return ( <p> Likes: {likes} <button onClick={like}>Like</button> </p> ); }Choose an answer to see the explanation.
Key takeaways
- A local variable can't hold a component's memory: changing it doesn't ask React to render, and every render makes it again.
useState(initial)returns[value, setValue]. The initial value counts on the first render only; after that, React hands back what it kept.- Calling the set function stores the new value and asks React to render the component again.
- A component can have many state variables. Keep only what changes and can't be worked out from props or other state.
- Every copy of a component has its own, private state. To share a value, keep it in the parent and pass it down.
- React matches
useStatecalls to their values by order, so hooks go at the top level of the component.
Remember the one-liner.
State is what a component remembers between renders; set it, and React renders again.
Coming next: Render and commit. It follows what happens after you call a set function: React renders your components, changes only what differs on the page, and the browser paints.