React State — useState
What is State?
State is data that a component owns and can change over time. When state changes, React automatically re-renders the component to reflect the new data. State is the mechanism that makes React UIs interactive.
The useState hook is the primary way to add state to function components. It returns a pair: the current state value and a function to update it.
import { useState } from 'react'
// Basic counter
function Counter() {
// useState(initialValue) 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>
)
}
// Functional update — use when new state depends on old state
function SafeCounter() {
const [count, setCount] = useState(0)
// WRONG — may use stale state in async contexts:
// setCount(count + 1)
// CORRECT — always gets latest state:
const increment = () => setCount(prev => prev + 1)
const decrement = () => setCount(prev => prev - 1)
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
// Toggle state
function Toggle() {
const [isOn, setIsOn] = useState(false)
return (
<button onClick={() => setIsOn(prev => !prev)}>
{isOn ? 'ON' : 'OFF'}
</button>
)
}
// Multiple state variables
function Form() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [age, setAge] = useState(0)
return (
<form>
<input value={name} onChange={e => setName(e.target.value)} placeholder="Name" />
<input value={email} onChange={e => setEmail(e.target.value)} placeholder="Email" />
<input value={age} onChange={e => setAge(Number(e.target.value))} type="number" />
</form>
)
}
import { useState } from 'react'
// Object state — MUST spread to update
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
role: 'user'
})
// WRONG — mutates state directly:
// user.name = 'Alice' // React won't re-render!
// CORRECT — create new object with spread:
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"
/>
<select
value={user.role}
onChange={e => updateField('role', e.target.value)}
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<pre>{JSON.stringify(user, null, 2)}</pre>
</form>
)
}
import { useState } from 'react'
// Array state — MUST use non-mutating methods
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build a project', done: false },
])
const [input, setInput] = useState('')
// Add item
const addTodo = () => {
if (!input.trim()) return
setTodos(prev => [
...prev,
{ id: Date.now(), text: input, done: false }
])
setInput('')
}
// Toggle done
const toggleTodo = (id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
)
}
// Delete item
const deleteTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id))
}
return (
<div>
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
<span onClick={() => toggleTodo(todo.id)}>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>✕</button>
</li>
))}
</ul>
<p>{todos.filter(t => t.done).length} / {todos.length} done</p>
</div>
)
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.