useState Hook
useState lets you add state to a function component. It returns a pair: the current state value and a function to update it.
Syntax
const [state, setState] = useState(initialValue);
// initialValue can be a value OR a function (lazy initialisation)
const [expensive, setExpensive] = useState(() => computeExpensiveDefault());
All State Types
// String
const [name, setName] = useState("");
// Number
const [count, setCount] = useState(0);
// Boolean
const [isOpen, setIsOpen] = useState(false);
// Array
const [items, setItems] = useState([]);
// Object
const [user, setUser] = useState({ name: "", email: "" });
// null (data not yet loaded)
const [data, setData] = useState(null);
Functional Updates โ The Safe Pattern
function Counter() {
const [count, setCount] = useState(0);
// โ Can produce stale state in async contexts
const incrementBad = () => setCount(count + 1);
// โ
Always correct โ prev is guaranteed to be current
const incrementGood = () => setCount(prev => prev + 1);
// Multiple updates in one event โ without functional form only ONE update runs
const incrementThrice = () => {
setCount(prev => prev + 1); // โ
setCount(prev => prev + 1); // โ
setCount(prev => prev + 1); // โ
// Final count = prev + 3
};
}
Complex State Example โ Shopping Cart
function ShoppingCart() {
const [cart, setCart] = useState([]);
const addItem = (product) => setCart(prev => {
const exists = prev.find(i => i.id === product.id);
if (exists) {
return prev.map(i => i.id === product.id ? { ...i, qty: i.qty + 1 } : i);
}
return [...prev, { ...product, qty: 1 }];
});
const removeItem = (id) =>
setCart(prev => prev.filter(i => i.id !== id));
const totalPrice = cart.reduce((sum, i) => sum + i.price * i.qty, 0);
return (
<div>
<h2>Cart ({cart.length} items) โ ${totalPrice.toFixed(2)}</h2>
{cart.map(item => (
<div key={item.id}>
{item.name} ร {item.qty}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
</div>
);
}