- Home
- /
- Tutorials
- /
- React Tutorial
- /
- useState Explained
State and Reducers
useState Explained
A component function runs again on every render, so a plain variable resets every time. State is the box React keeps for you between those runs.
Why a normal variable fails
The component function re-runs from the top on each render. Any let inside it is created fresh, so an update made during one render is gone by the next - and nothing tells React to re-render in the first place.
Plain variable versus state
jsx
function BrokenCounter() {
let count = 0 // recreated on every render
return (
<button onClick={() => { count = count + 1 }}>
Plain variable: {count} (never changes)
</button>
)
}
function WorkingCounter() {
const [count, setCount] = React.useState(0)
return (
<button onClick={() => setCount(count + 1)}>
State: {count}
</button>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div style={{ display: "grid", gap: 8, justifyItems: "start" }}>
<BrokenCounter />
<WorkingCounter />
</div>
)The first button really does increment its variable - you just never see it, because nothing asks React to render again and the value is discarded when it does.
The two things you get back
useState returns an array of exactly two items, which is why the line is always written with array destructuring.
- The current value for this render.
- A setter that stores a new value and schedules a re-render.
The names are yours to choose, but the convention [thing, setThing] is universal and worth following - deviating from it makes React code read as unfamiliar for no gain.
The argument is the initial value only
The value passed to useState is used on the first render and ignored on every one after. This confuses people who pass a prop and expect the state to follow when the prop changes - it does not.
Initial means initial
jsx
function Field({ startWith }) {
// Only read once. Changing startWith later will not update this.
const [text, setText] = React.useState(startWith)
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>Prop was: "{startWith}" — state is now: "{text}"</p>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<Field startWith="hello" />)State is per component instance
Render the same component twice and each copy gets its own state. They share code, not values - which is what makes a component reusable rather than a global.
Two instances, two counts
jsx
function Counter({ label }) {
const [count, setCount] = React.useState(0)
return (
<button onClick={() => setCount(count + 1)}>
{label}: {count}
</button>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div style={{ display: "flex", gap: 8 }}>
<Counter label="First" />
<Counter label="Second" />
</div>
)- State survives re-renders; a plain variable does not.
- The setter is what schedules the re-render.
- The argument is the initial value, read once.
- Every instance of a component has its own state.
