React State & Hooks — The Basics
Build a real mental model of React state: what state is, how the useState hook returns a value and a setter, why calling the setter triggers a re-render, updating state immutably, and how state differs from props.
Introduction
In React, state is the data a component remembers between renders and can change over time. A plain variable inside a component is reset to its starting value every time the function runs, so it cannot hold anything that should survive a click or a keystroke. State is React's answer: a value that persists across renders and, crucially, one that tells React to redraw the screen whenever it changes. If you have ever wanted a counter to keep counting or an input to remember what the user typed, that memory is state.
Why does state exist at all? A React component is just a function that returns markup. Functions run top to bottom and forget everything when they return, which is exactly the wrong behaviour for an interface that has to react to the user. State gives a component a small, private memory that outlives a single call, plus a rule the rest of React depends on: when that memory changes, the component runs again and the screen updates to match. Without state, a page would render once and never respond to anything.
The whole model rests on one tool — the useState hook — and one loop. You declare a piece of state, React hands you its current value and a function to change it, and calling that function schedules a render. Hold that loop in your head: read the value, call the setter to update it, watch React re-render with the new value. Every other piece of state you ever write is a variation on that single cycle. By the end of this page you will be able to reason about when a component re-renders, why you never assign to a state variable directly, and where state ends and props begin.
Lessons
1. What component state is
A component renders by running its function and returning JSX. Any ordinary variable you declare inside that function is born and dies with a single run, so it can never remember a change. State is the exception: a value React stores outside the function call and gives back to you on every render. That is the minimal, correct picture — state is memory that belongs to a component instance and persists as the component re-renders, where a normal variable would be wiped clean each time.
function Counter() {
let count = 0; // reset to 0 on every render — useless as memory
return <button onClick={() => count++}>{count}</button>;
}
The snippet above looks like it should count, but it never will: each render makes a fresh count of 0, and mutating it does not tell React to redraw. That is the gap state fills, and the next lesson introduces the hook that fills it.
2. The useState hook returns a value and a setter
You add state with the useState hook. Calling useState(initial) returns an array of exactly two things: the current value and a setter function that changes it. You almost always destructure them on one line. The name you give the setter is conventionally set followed by the value's name — count and setCount, name and setName. The argument you pass to useState is the initial value, used only on the very first render; after that React keeps track of the current value for you.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// count -> the current value
// setCount -> the function that updates it
}
That pair — [value, setter] — is the entire surface of the hook. The value is read-only from your point of view: you never assign to it directly. Instead you call the setter, which is what the next lesson is about.
3. Calling the setter triggers a re-render
Here is the load-bearing idea of the whole page. Calling the setter does two things: it stores the new value, and it asks React to render the component again. On that next render, useState returns the value you just set, the JSX is recomputed, and React updates the DOM to match. This is why you change state through the setter and never by mutating the variable — a direct assignment changes a number in memory but never schedules the render, so the screen goes stale. The setter is the trigger; the re-render is how the screen stays in sync with the data.
const [count, setCount] = useState(0);
// wrong: React never re-renders, screen never updates
count = count + 1;
// right: store the new value AND schedule a render
setCount(count + 1);
Read that as a loop you can predict: an event calls the setter, the setter schedules a render, the component runs again with the fresh value, and the DOM updates. Understanding this cycle is understanding React state.
4. Updating state immutably
You update state by handing the setter a new value, never by editing the old one in place. For a number or string that is natural — setCount(count + 1). For objects and arrays it matters more: you build a new object or array rather than mutating the existing one, because React decides whether to re-render by comparing the new value to the old, and an in-place mutation looks unchanged. When the next value depends on the previous one, prefer the updater-function form: pass a function that receives the current value and returns the next one, which stays correct even when several updates are batched together.
// updater-function form — next value from the previous one
setCount((c) => c + 1);
// objects: build a new one, do not mutate
setUser((prev) => ({ ...prev, name: 'Ada' }));
The rule is one sentence: replace, do not edit. Give the setter the next value — copying the old data and changing the copy — and React reliably detects the change and re-renders.
5. State versus props
State and props are both data a component uses, but they flow from opposite directions. State is owned by the component and is private to it — the component creates it, reads it, and updates it with a setter. Props are passed in from a parent and are read-only to the child; a child never changes its own props. A useful test: if a value is something this component controls and changes over time, it is state; if it is configuration handed down from above, it is a prop. Often a parent holds a piece of state and passes it — and a setter — down to children as props, which is how data and the ability to change it travel through a tree.
function Parent() {
const [text, setText] = useState(''); // state lives here
return <Child value={text} onChange={setText} />; // passed down as props
}
6. When to use state
Reach for state only for data that changes over time and should redraw the screen when it does — what the user typed, whether a menu is open, the current count, data that has loaded. If a value can be computed from existing state or props during render, do not store it in state; derive it instead, so there is nothing to keep in sync. Keep each piece of state as small and as low in the tree as it can live, and lift it to a shared parent only when two components genuinely need the same value. That is the complete model: state is a component's changeable memory, the useState hook exposes it as a value and a setter, calling the setter updates the value and triggers a re-render, you always replace rather than mutate, and props are the read-only data that flows in from above.
Practice
Predict first: in the counter below, what does count equal on the render immediately after the first click, and why does the button text change at all? (Answer: it becomes 1, because setCount both stores the new value and schedules a render, and on that render useState returns 1.) Now run it and click a few times — this is the whole loop in one small component:
State does not have to be a number. A boolean is just as common — an open/closed menu, a shown/hidden panel, a light that is on or off. The setter still drives the re-render; here the updater form flips the previous value, which is the safe way to toggle. Click to switch it:
State can also hold an object. The rule from lesson 4 applies: never edit the object in place — build a new one so React sees the change and re-renders. This counter keeps a name and a score in one state object and bumps the score with the spread form, copying the old fields and replacing just the one that changed:
Next steps
You have the model — now put it to work. Read the useState hook reference for the full signature and the updater form, learn how to handle form input for the most common state task, and browse the runnable React hooks examples for copy-paste patterns. When you need to run code after a render — a subscription, a timer, a fetch — step up to the useEffect hook reference. Curious how another framework tracks changing data? Compare Vue reactivity. Every snippet here runs in the editor.
Frequently asked questions
What is state in React?
What does the useState hook return?
const [count, setCount] = useState(0). The argument is the initial value, used only on the first render.Why does calling the setter re-render the component?
useState returns the updated value and React updates the DOM. Assigning to the variable directly skips the render, so the screen never updates.