useState is a React hook that lets you add state (memory) to a functional component. When the state changes, React automatically re-renders the component to display the latest value.
Why Not Just Use a Regular Variable?
A natural question is: why not just use a let variable instead? The answer is that regular variables do not trigger re-renders. React has no way of knowing a regular variable changed, so the UI stays the same.
function Counter() {
let count = 0; // Regular variable
return (
<div>
<p>{count}</p>
<button onClick={() => { count += 1; console.log(count); }}>
Click Me
</button>
</div>
);
}
The console.log will show the count increasing, but the UI will never update. React only re-renders a component when its state changes — and useState is how you declare state.
Syntax
import { useState } from "react";
function Example() {
const [count, setCount] = useState(0); // count = state, setCount = updater
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click Me
</button>
</div>
);
}
Breakdown
| Part | What It Does |
|---|---|
useState(0) | Initializes state with value 0 |
count | The current state value |
setCount() | The function used to update the state |
onClick={...} | Updates state when the button is clicked |
| Re-rendering | UI automatically updates with the new value |
State Updates Are Asynchronous
State updates in React are not applied immediately — they are scheduled and batched. This means if you console.log the state variable right after calling the setter, you will still see the old value.
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // Still logs 0 — the update hasn't applied yet
}
The updated value will be available on the next render, not immediately after the setter is called.
The Updater Function Pattern
When the new state depends on the previous state, the safer approach is to use an updater function — a callback passed to the setter that receives the previous state as its argument.
// Less safe — relies on the current value of count in scope
setCount(count + 1);
// Safer — always uses the most recent state value
setCount(prev => prev + 1);
This matters especially when multiple state updates happen in the same render cycle, where the direct approach can produce stale values.
Updating Object and Array State
When state is an object or array, you must never mutate it directly. Instead, create a new copy with the changes applied. React uses reference equality to detect changes — mutating the original means React sees the same reference and skips the re-render.
Updating Object State
const [user, setUser] = useState({ name: "Wariz", age: 20 });
// ❌ Wrong — mutates the original object
user.age = 21;
setUser(user);
// ✅ Correct — creates a new object with the spread operator
setUser({ ...user, age: 21 });
Updating Array State
const [items, setItems] = useState(["Apple", "Banana"]);
// ❌ Wrong — mutates the original array
items.push("Cherry");
setItems(items);
// ✅ Correct — creates a new array
setItems([...items, "Cherry"]);
Typing State with TypeScript
In TypeScript, you can explicitly type your state using the generic syntax useState<Type>. TypeScript can often infer the type from the initial value, but explicit typing is useful for complex types like objects and arrays.
// TypeScript infers this as number
const [count, setCount] = useState(0);
// Explicit typing for an object
interface User {
name: string;
age: number;
}
const [user, setUser] = useState<User>({ name: "Wariz", age: 20 });
// Explicit typing for an array
const [items, setItems] = useState<string[]>([]);
Typing state explicitly is especially important for arrays initialized as [] — without a type, TypeScript infers never[], which means nothing can be added to it.
More Examples
Boolean State — Like Button
import { useState } from "react";
function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "❤️ Liked" : "🤍 Like"}
</button>
);
}
export default LikeButton;
Boolean State — Login Toggle
import { useState } from 'react';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<p>{isLoggedIn ? 'Welcome back!' : 'Please log in'}</p>
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? 'Logout' : 'Login'}
</button>
</div>
);
}
export default App;