Lesson 3 of 12
State & useState
You will learn how to give a component its own changing data with `useState`. When state changes, React updates the screen to match.
import { useState } from 'react'
function Counter() {
// useState returns [currentValue, setterFunction]
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
)
}
// When state depends on previous value, use the updater form
function SafeCounter() {
const [count, setCount] = useState(0)
const increment = () => setCount(prev => prev + 1)
// Always safe, even with multiple rapid updates
return <button onClick={increment}>{count}</button>
}import { useState } from 'react'
// State can be any value: string, number, object, array
function ProfileEditor() {
const [user, setUser] = useState({
name: "Alice",
email: "alice@example.com",
bio: "",
})
// Always create new objects/arrays — never mutate state directly
const updateField = (field, value) => {
setUser(prev => ({ ...prev, [field]: value }))
}
return (
<form>
<input
value={user.name}
onChange={e => updateField("name", e.target.value)}
placeholder="Name"
/>
<input
value={user.email}
onChange={e => updateField("email", e.target.value)}
placeholder="Email"
/>
<textarea
value={user.bio}
onChange={e => updateField("bio", e.target.value)}
placeholder="Bio"
/>
<pre>{JSON.stringify(user, null, 2)}</pre>
</form>
)
}
// Array state — add, remove, update items
function TodoApp() {
const [todos, setTodos] = useState([])
const [text, setText] = useState("")
const add = () => {
if (!text.trim()) return
setTodos(prev => [...prev, { id: Date.now(), text, done: false }])
setText("")
}
const toggle = (id) => setTodos(prev =>
prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
)
const remove = (id) => setTodos(prev => prev.filter(t => t.id !== id))
return (
<div>
<input value={text} onChange={e => setText(e.target.value)}
onKeyDown={e => e.key === "Enter" && add()} />
<button onClick={add}>Add</button>
{todos.map(t => (
<div key={t.id}>
<input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} />
<span style={{ textDecoration: t.done ? "line-through" : "none" }}>{t.text}</span>
<button onClick={() => remove(t.id)}>✕</button>
</div>
))}
</div>
)
}Common question
Can I change React state by assigning to it?
A component is a that takes props and returns what should be on screen. State is memory attached to that component between calls. useState returns the value for this render and a setter; calling the setter stores a new value and asks React to render again, which runs the function fresh with the new snapshot.
Three habits follow. Never mutate state; create a new value. Use the updater form when the next value depends on the current one. And keep state small: anything you can compute from existing state or props during render should be computed, not stored.
Source: The React documentation describes state as "a snapshot": setting it a new render with the new value and does not change the in the render that is already running.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
State is a variable, so count = count + 1 should update it.
Pushing to a state array updates the component, because the array changed.
After calling the setter, the variable holds the new value on the next line.
Tip: use the ← and → keys to move between lessons.