State with useState
Learn how useState gives your React components memory — letting them track data that changes over time and automatically update the screen when it does.
So far you've built React components that always display the same thing — they receive props and render HTML. That's great for static content, but real apps need to react to the user. A button that counts clicks. A toggle that shows or hides a menu. A form that tracks what you've typed.
All of these need state — data that can change over time and that should cause the component to update what it shows. This is where React's useState hook comes in, and it's one of the most satisfying moments in learning React.
#What Is State, Exactly?
A Scoreboard at a Sports Game
Think of a scoreboard at a basketball game. The scoreboard always shows the current score. When a team scores, the number changes and the scoreboard immediately reflects the new value.
In React, state is your component's scoreboard. It holds the current value of something that can change. When you update state, React automatically re-renders the component — just like the scoreboard updating the moment a basket is scored. You never have to manually touch the DOM.
State is different from props in one key way: a component owns and controls its own state, whereas props are handed down from a parent. If you need a number that changes when a button is clicked, that number lives in state — not in props.
#Importing and Calling useState
useState is a hook — a special function React provides that lets you "hook into" React features from inside a function component. You import it from the react package and call it at the top of your component.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}Let's unpack that one line: const [count, setCount] = useState(0);
- `useState(0)` — calls the hook and tells React: "this state variable starts at
0". - `count` — the current value of the state. Use this anywhere you'd use a regular variable.
- `setCount` — the setter function. Call it whenever you want to change the value. React will update
countand re-render the component automatically.
The square brackets are JavaScript array destructuring — useState returns a two-element array, and you're naming those two elements.
#Why Never Mutate State Directly
Never Do This
``jsx // WRONG — React won't know anything changed! count = count + 1; ``
If you change count directly, React has no idea the value changed, so it never re-renders. The screen stays frozen even though the variable updated. Always use the setter function (setCount) — that's the signal React listens for.
React works by comparing the previous state to the new state. When you call setCount(newValue), React schedules a re-render with the updated value. Mutating the variable directly bypasses this system entirely — it's like changing the scoreboard's wiring without flipping the switch.
#The Updater Function Form
Most of the time passing a value directly works fine: setCount(count + 1). But there's a subtle problem if you call the setter multiple times in one event handler — React batches those updates, so each call might read the same stale value of count.
The safe pattern is the updater function form: instead of passing the next value, you pass a function that receives the guaranteed-current previous value and returns the next one.
// Directly passing a value — usually fine
setCount(count + 1);
// Updater function form — always safe
setCount(prevCount => prevCount + 1);
// Example: incrementing twice correctly
function handleDoubleClick() {
setCount(prev => prev + 1); // first
setCount(prev => prev + 1); // second — reads updated prev!
}Naming Convention
By convention, name the previous-value argument with a prev prefix: prevCount, prevItems, prevIsOpen. This makes it immediately clear you're working with the previous state, not some other variable.
#State Can Hold Any Value
useState isn't limited to numbers. You can store strings, booleans, arrays, objects — anything JavaScript can hold.
function ToggleMessage() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button onClick={() => setIsVisible(prev => !prev)}>
{isVisible ? 'Hide' : 'Show'} message
</button>
{isVisible && <p>Hello there! ��</p>}
</div>
);
}Each Component Instance Has Its Own State
If you render <Counter /> twice on the same page, each one gets its own completely independent count. Clicking the button on one counter has zero effect on the other. State lives inside the component instance, not globally.
You want to increment a counter by 1. Which of the following is the SAFEST way to update state?
Key takeaways
- `useState(initialValue)` returns `[currentValue, setterFn]` — use the setter to update, never mutate directly.
- Calling the setter tells React the value changed, triggering an automatic re-render of the component.
- Use the updater function form `setState(prev => ...)` whenever the new value depends on the previous one.
- Each component instance has its own independent copy of its state.
- State can hold any JavaScript value: numbers, strings, booleans, arrays, or objects.
This code has a bug — what's wrong?
function Counter() {
let count = 0;
return (
<button onClick={() => count = count + 1}>
Clicked {count} times
</button>
);
}Complete this counter's state declaration so it starts at 0 and gives you both the current value and a setter. Fill in the hook being called.
import { useState } from 'react'; function Counter() { const [count, setCount] = (0); return <button onClick={() => setCount(count + 1)}>{count}</button>; }
Using the updater function form, what number does the paragraph show after ONE click of the button?
const [count, setCount] = useState(0);
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}
// <p>{count}</p> and a button wired to handleClickArrange these lines into a correct ToggleMessage component that uses a boolean state to show or hide a paragraph and flips it with the updater form.
);
</div>
}
return (
{isVisible && <p>Hello there!</p>}<div>
<button onClick={() => setIsVisible(prev => !prev)}>Toggle</button>const [isVisible, setIsVisible] = useState(false);
function ToggleMessage() {Build a LikeButton component. It should display a count starting at 0 and a button labeled "Like". Each time the button is clicked, the count should go up by 1. Use the updater function form of setCount.
Try it live — edit the code and hit Run to see it rendered: