State: useState
State is data a component remembers between renders: a counter value, the text in a field, whether a menu is open. When state changes, React calls the component function again and updates the page. State is created with the useState hook. A hook is a React function whose name starts with use; you call it inside a component.
A first counter
Create src/components/Counter.jsx:
// src/components/Counter.jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Clicks: {count}</button>;
}Render <Counter /> in src/App.jsx. The button shows "Clicks: 0", and the number grows with every click.
How it works
useState(0) returns an array of two elements, which is unpacked right away:
| Element | What it is |
|---|---|
count | The current value, 0 the first time |
setCount | A function that sets a new value and triggers a re-render |
The order is: a click calls setCount(1) → React calls Counter again → useState now returns 1 → the page shows the new text. You choose the names; the convention is the pair something and setSomething.
State in the to-do list
Let us add a button that shows and hides finished tasks. Change src/App.jsx:
// src/App.jsx
import { useState } from 'react';
import TodoList from './components/TodoList.jsx';
const todos = [
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Call Oleg', done: true },
];
export default function App() {
const [showDone, setShowDone] = useState(true);
const visible = showDone ? todos : todos.filter((todo) => !todo.done);
return (
<>
<button onClick={() => setShowDone(!showDone)}>
{showDone ? 'Hide finished' : 'Show finished'}
</button>
<TodoList todos={visible} />
</>
);
}Clicking the button hides "Call Oleg", clicking again brings it back. The button text changes together with the state.
Updating from the previous value
When the new value depends on the old one, it is safer to pass a function to setCount — it receives the up-to-date value:
// src/components/Counter.jsx
function handleAddTwo() {
setCount((value) => value + 1);
setCount((value) => value + 1);
}This handler adds 2. Two calls of setCount(count + 1) in a row would add only 1: count does not change inside one handler until the next render.
Each instance has its own state
If you render <Counter /> twice, each button has its own number: state belongs to a specific place in the component tree, not to the function.
Common mistake
A plain variable does not work as state. Changing it does not trigger a re-render, and on the next render it is created again:
export default function Counter() {
let count = 0;
// The number on the page never changes.
return <button onClick={() => count++}>Clicks: {count}</button>;
}Keep everything that must change on screen in useState and change it only with the setter function.