So far your components only show static stuff. Real apps have to respond to people, though. A counter that goes up when you click it. A form that keeps track of what you type. A toggle that flips between light and dark mode. Every one of those needs state.
What is State?
State is data that can change over time, and when it changes, your component re-renders. Think of it as your component's memory... it remembers values from one render to the next.
Here's the big difference between a regular variable and state.
Example
// This does NOT work - the UI won't update
function BrokenCounter() {
let count = 0;
return (
<button onClick={() => { count += 1; }}>
Clicked {count} times
</button>
);
}
Click that button and count really does change in memory. React just has NO idea it happened. So the screen sits there at "Clicked 0 times" forever (super frustrating the first time you hit it). Regular variables don't trigger re-renders.
Enter useState
The useState hook is how React gives a component memory. It hands you back two things, the current value and a function to update it.
Example
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
Now when you click the button, setCount basically tells React "hey, the count changed!" React re-renders the component with the new value and the screen updates. Donezo.
Breaking Down useState
Step 1. Import the Hook
Before you can use useState, you have to import it from React.
Example
import { useState } from 'react';
Step 2. Call useState with an Initial Value
Pass in the starting value as an argument. This value only matters on the very first render.
Example
const [count, setCount] = useState(0); // starts at 0
const [name, setName] = useState(''); // starts as empty string
const [items, setItems] = useState([]); // starts as empty array
const [user, setUser] = useState(null); // starts as null
Step 3. Use Destructuring to Get the Value and Setter
useState returns an array with exactly two items in it. You can name them whatever you want, but the convention everybody follows is [thing, setThing].
When you call setIsOn(!isOn), React schedules a re-render. Your component function runs again, useState hands back the new value, and the UI updates to match.
Updating State Based on Previous Value
When your new state depends on the old state, use the functional form of the setter.
Example
// Less reliable (might use stale value):
setCount(count + 1);
// More reliable (always uses the latest value):
setCount(prev => prev + 1);
The functional form gets handed the most current value as an argument. That matters when a bunch of updates happen quickly or inside event handlers, where the plain version can grab a stale value without you noticing.
Multiple State Variables
A component can have as many useState calls as it needs. There's no limit.
You can store objects and arrays in state too. Just remember you have to create a new object or array whenever you update it, instead of changing (mutating) the one you already have.
Example
function TodoList() {
const [todos, setTodos] = useState([]);
function addTodo(text) {
// Create a new array with the existing items plus the new one
setTodos([...todos, { id: Date.now(), text }]);
}
function removeTodo(id) {
// Create a new array without the removed item
setTodos(todos.filter(todo => todo.id !== id));
}
return (
<div>
<button onClick={() => addTodo('New task')}>Add</button>
{todos.map(todo => (
<p key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</p>
))}
</div>
);
}
TL;DR
State is data that changes over time and triggers a re-render when it's updated
useState returns an array, [currentValue, setterFunction]
Always update state with the setter function (never change it directly)
Use the functional form (prev => prev + 1) when the new value depends on the old one
One component can have several useState calls for different pieces of data
When you update objects or arrays, make a new copy instead of mutating the old one
What's Next?
Your components can remember and update data now. Next you'll learn how to show or hide things based on that data. That's conditional rendering, which is just a fancy way of saying "show different UI depending on what's in state"...