State with useState
Props let parents pass data down. But what about data that changes inside a component? That's what state is for. State is local, mutable data owned by a component.
The useState Hook
useState is the most-used React hook. It returns two things: the current value, and a function to update it.
import { useState } from 'react';
function Counter() {
// useState(0) sets initial value to 0
// 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>
);
}
How State Works
When you call the setter function (setCount), React:
- Stores the new state value
- Re-renders the component with the new value
- Updates the DOM with only what changed
Never mutate state directly! Always use the setter function. Doing count++ instead of setCount(count + 1) won't trigger a re-render.
State Can Hold Anything
State can be a number, string, boolean, array, or object:
import { useState } from 'react';
function Examples() {
const [count, setCount] = useState(0); // number
const [name, setName] = useState("Alex"); // string
const [isVisible, setIsVisible] = useState(true); // boolean
const [items, setItems] = useState([]); // array
const [user, setUser] = useState({ name: "", age: 0 }); // object
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "Hide" : "Show"}
</button>
{isVisible && <p>Now you see me!</p>}
</div>
);
}
Updating Object State
When state is an object, you must spread the previous state to preserve other fields:
import { useState } from 'react';
function UserForm() {
const [user, setUser] = useState({ name: "", email: "", age: 0 });
function updateName(newName) {
// Spread operator preserves other fields
setUser({ ...user, name: newName });
// Without spread: setUser({ name: newName })
// That would erase email and age!
}
return (
<div>
<input value={user.name} onChange={e => updateName(e.target.value)} />
<p>Name: {user.name}, Email: {user.email}</p>
</div>
);
}
Multiple State Variables
Use multiple useState calls — one for each independent piece of state:
import { useState } from 'react';
function SearchBar() {
const [query, setQuery] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [results, setResults] = useState([]);
const [error, setError] = useState(null);
async function search() {
setIsLoading(true);
setError(null);
try {
const data = await fetchResults(query);
setResults(data);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
}
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button onClick={search} disabled={isLoading}>
{isLoading ? "Searching..." : "Search"}
</button>
{error && <p className="error">{error}</p>}
{results.map(r => <div key={r.id}>{r.title}</div>)}
</div>
);
}
Keep related data in one state object, and unrelated data in separate useState calls.