Introduction: What is useState?
React is a powerful JavaScript library for building interactive user interfaces — and at the core of its interactivity lies something called state. State is what allows your app to change over time, respond to user actions, and update the UI dynamically.
The useState() hook is the simplest and most commonly used React hook. It enables function components to store and update state — previously only possible in class components. If you're using modern React (and you are), understanding useState is non-negotiable.
- What useState() does
- How to use it
- When to use it
- Common patterns and mistakes
- Best practices for 2025
What is useState()?
useState() is a function from the React library that allows you to add local state to functional components.
Before hooks were introduced in React 16.8, managing state was only possible in class-based components. Now, with useState(), you can write cleaner, smaller, and more maintainable functional components — with built-in reactivity.
Basic Syntax
const [state, setState] = useState(initialValue);- state : the current state value
- setState : a function used to update the state
- initialValue : the default value for the state when the component mounts
Example 1: Basic Counter
Let’s look at the most common example — a button that increments a number on click.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}Each time the button is clicked, setCount updates the state, and the component re-renders with the new value.
State Can Be Any Type
The beauty of useState is that it works with any data type — strings, booleans, arrays, objects, or even null.
Examples
const [name, setName] = useState("React");
const [isOpen, setIsOpen] = useState(false);
const [todos, setTodos] = useState([]);
const [user, setUser] = useState({ name: "", email: "" });You can use useState to control everything from dropdown visibility to form data and list items.
Updating State Correctly
To update state, always use the updater function (setState) — never mutate the state directly.
Example
setCount(count + 1);
// For updates that depend on previous state:
setCount(prev => prev + 1);This avoids bugs related to stale closures, especially when calling setState multiple times in a single event.
Avoid Direct Mutation
One of the most common mistakes developers make is mutating the state instead of updating it immutably.
Bad:
todos.push("New Task");
setTodos(todos);Good:
setTodos([...todos, "New Task"]);React depends on immutability to detect changes — so always return a new object or array when updating state.
Best Practices for useState (2025 Edition)
Use one state per logical concern
const [name, setName] = useState("");
const [email, setEmail] = useState("");Initialize with the correct type
// TypeScript example:
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);Use updater functions when working with previous state
setCount(prev => prev + 1);Avoid nesting state updates
If your component’s logic is getting complicated, consider switching to useReducer() — it's more predictable for complex workflows.
When to Use useState
The useState hook is perfect for:
- Form inputs (text, checkbox, radio)
- Modal visibility (open/close state)
- Tabs and toggles
- Count and rating systems
- Theme or language toggles (for small apps)
- Shared state across multiple components
- Complex data logic with multiple dependencies
- Global state like auth, roles, or large forms
- Context API for simple global state
- useReducer() for complex logic
- State management libraries like Zustand or Redux for large-scale apps
Real-World Example: Managing Form State
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit">Login</button>
</form>
);
}Clean, reactive, and easy to extend.
Common useState Pitfalls (and Fixes)
Direct mutation of arrays/objects
Use spread syntax or functional updates
Ignoring previous state dependency
Use updater function: setCount(prev => …)
Using one large object for all state
Split into multiple useState() calls
Trying to share state with siblings
Use Context or lift state to parent
Summary
The useState() hook is the foundation of interactivity in React’s modern, functional approach. It allows your components to respond to users, change dynamically, and keep the UI in sync with your data.
What you’ve learned:
- The syntax and signature of useState
- How to initialize and update state correctly
- Examples for counters, form fields, booleans, and lists
- How to avoid common mistakes
- When to use useState , and when not to