useState
Master useState — the most fundamental React hook. Learn how it works under the hood, the patterns senior engineers use, and the bugs that trip up even experienced developers.
Last reviewed: January 2025
🔍 What Is It?
useState is a React hook that lets you add reactive state to a function component.
const [value, setValue] = useState<Type>(initialValue);- →
value— the current state (read-only inside this render) - →
setValue— the updater function that schedules a re-render - →
initialValue— the value on the first render only (ignored on subsequent renders)
Two update forms:
- →
setValue(newValue)— direct update - →
setValue(prev => prev + 1)— functional update, safe when new value depends on old value
🎯 Why Do We Need It?
Before hooks, only class components could have state. Function components were purely presentational. useState changed that — now any function can hold reactive data.
Without useState, you cannot build:
- →A form that tracks user input
- →A button that shows loading state
- →A modal that opens and closes
- →A counter, a toggle, a dropdown — any interactive UI element
useState is the foundation of all React interactivity.
🏗️ What Happens in Real Projects?
In an e-commerce checkout flow:
- →
const [step, setStep] = useState<1|2|3>(1)— which checkout step is active - →
const [cart, setCart] = useState<CartItem[]>([])— items in the cart - →
const [coupon, setCoupon] = useState('')— coupon code input - →
const [processing, setProcessing] = useState(false)— payment in flight - →
const [error, setError] = useState<string | null>(null)— payment error message
⚙️ How It Works
Under the hood:
React maintains a list of state slots for each component instance. On the first render, useState initialises the slot. On subsequent renders, it returns the current value from that slot. This is why hooks must always be called in the same order — React uses call order to match hooks to their slots.
Batching (React 18+):
Multiple setState calls in the same event handler are batched — React runs only one re-render for all of them. In React 18, this batching also applies inside async functions, timeouts, and promises.
Object state:
useState does a shallow replace, not a deep merge. If your state is an object, you must spread the previous state: setUser(prev => ({ ...prev, name: 'Alice' })). Forgetting the spread overwrites the entire object.
💻 Example
useState Patterns You Will Use Daily — Primitives, objects, arrays, and the async state pattern.
🤔 Common Confusions
❓ Why does my state show the old value right after setState?
setState is asynchronous — it schedules a re-render but the current render's variable is already captured. console.log(count) after setCount(5) still shows the old count. The new value is available in the next render. For logic that needs the new value immediately, compute it as a local variable first.
❓ What is the stale closure problem?
A function defined inside a component captures the state value at the time it was created. If state updates but the function is not recreated, it still sees the old value. Fix: use the functional update form setCount(prev => prev + 1) so the update always uses the latest value, regardless of closure.
❓ When should I use multiple useState vs one object?
Use multiple useState for independent values that change separately (isOpen, activeTab, searchTerm). Use one object for related values that often change together (form fields, async state: {data, loading, error}). The rule: if you always update them together, group them.
🚨 Common Production Issues
**Bug: Stale state in async callbacks** — An API callback captures the state from when the effect ran. By the time it resolves, state has changed. Fix: use functional updates or useRef to track the latest value.
**Bug: Object state overwrite** — setUser({ name: "Alice" }) throws away all other user fields. Always spread: setUser(prev => ({ ...prev, name: "Alice" })).
**Performance: Initialising state with expensive computation** — useState(computeHeavyValue()) runs on every render. Fix: pass a function useState(() => computeHeavyValue()) — runs only once.
**Bug: State not updating in loops** — Calling setCount(count + 1) three times in a loop gives count + 1, not count + 3, because count is the same snapshot in all three calls. Fix: setCount(prev => prev + 1).
✅ Best Practices
Use functional updates when new state depends on previous state
Use lazy initialisation for expensive initial values: useState(() => fn())
Group related state into objects; keep independent state separate
Always spread object state when doing partial updates
Use immutable array patterns (map, filter, spread) — never push/splice
Name state variables clearly: isLoading not loading, selectedUserId not user
Co-locate state with the component that uses it — do not hoist unnecessarily
🏛️ Architect's Note
useState is the right tool for local, component-specific state. The mistake teams make is using useState for everything — including server data, global UI state, and complex multi-step workflows. Server data belongs in React Query. Global UI state (sidebar open, theme) belongs in Context or Zustand. Complex workflows belong in useReducer. useState is powerful but has a clear scope: simple, local, component-owned state.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
↗ Where this topic fits
React Technology Guide
Return to the React learning hub.
Frontend Engineer Career Path
A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.
Python Developer Career Path
A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.
Sources
Sources / References
Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide React learning path
Browse the full React topic collection and learning order on SplashRide.
React Interview Prep
Practice React interview questions and scenario-based answers.
Frontend Engineer Career Path
A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.
Python Developer Career Path
A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.
React Controlled Components: Inputs, Checkboxes, and Forms
Build React controlled inputs with value, checked, and onChange. Includes a complete form, reset behavior, number fields, and fixes for uncontrolled-input warnings.
State
Understand React state — the internal memory of a component that drives UI updates. Learn when to use local state, how to structure it, and the patterns senior engineers use to avoid the most common state bugs.