Suggest an editImprove this articleRefine the answer for “How useState works in React?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**useState** is React's hook for adding local state to functional components. ```jsx const [count, setCount] = useState(0); setCount(5); // set to a specific value setCount(c => c + 1); // functional update: reads latest queued state ``` **Key:** the setter queues an update, not an instant change. Use the functional form when the new value depends on the current one.Shown above the full answer for quick recall.Answer (EN)Image**useState** is React's hook that lets a functional component own local state, returning a current value and a setter function that schedules a re-render. ## Theory ### TL;DR - Think of a vending machine display: shows the current count, you press a button (the setter) to queue a new value for the next cycle (re-render). - State survives re-renders. A plain variable declared inside a component resets every time the function runs. - The setter does not update state instantly. It queues an update, and React batches multiple calls into one re-render. - Use `useState` for local UI data. For shared state across components, use `useContext`. For complex update logic, use `useReducer`. - When the new value depends on the current one, always use the functional form: `setState(prev => next)`. ### Quick example ```jsx import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); // [current value, setter] return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}> + {/* functional update reads latest queued value */} </button> </div> ); } ``` `useState(0)` sets the initial value to `0`. Clicking the button calls the setter with a function that receives the latest state and returns the next value. React schedules a re-render and the component shows `1`. ### Key difference Unlike a plain variable, state persists between renders. But the setter does not change the value mid-render. It enqueues an update, and React applies all queued updates together before the next paint. This is why reading `count` right after `setCount(count + 1)` still gives you the old value. The new value only exists in the next render's scope. ### When to use - Local UI toggle (modal open/close, dropdown) - one `useState` per boolean. - Controlled form inputs - `useState` per field, or a single `useState({})` for the whole form. - Values that change independently - separate `useState` calls, not one large object. - Update logic that involves multiple sub-values or conditions - `useReducer` is a cleaner fit. - State shared by several components - lift it up or use `useContext` with a top-level `useState`. - New value depends on current state - always use the functional form to avoid stale reads. ### How useState stores state React keeps each component's hook data in a **fiber node**, an internal object in the component tree. Each `useState` call gets one slot in a linked list attached to that fiber. The first `useState` maps to slot 0, the second to slot 1, and so on. This is exactly why calling hooks conditionally breaks React. If a `useState` sits inside an `if` block, the slot assignment shifts between renders and React reads the wrong state from the wrong slot. The "only call hooks at the top level" rule exists for this reason. When you call the setter (internally called `dispatchAction`), React adds an update object to that slot's queue. On the next render, React replays all hooks in order, processes each update queue, and computes new state. In React 18, updates inside events, `setTimeout`, and `Promise.then` are all batched automatically into one re-render. In React 17, only event handler updates were batched. ### Lazy initialization Pass a function to `useState` when computing the initial value is expensive: ```jsx // Runs compute() on every render, result ignored after mount const [data, setData] = useState(compute()); // Runs compute() once on mount only const [data, setData] = useState(() => compute()); ``` This pattern is called lazy initialization. React calls the function exactly once and uses the return value as the starting state. Good candidates: reading from `localStorage`, parsing a URL, or processing a large initial dataset. ### Common mistakes **Mutating state directly:** ```jsx const [arr, setArr] = useState([1, 2]); arr.push(3); // React sees the same reference, no re-render setArr(arr); // Same reference again, React bails out // Fix: setArr([...arr, 3]); // or functional form: setArr(a => [...a, 3]); ``` **Reading state right after setting it:** ```jsx const handleClick = () => { setCount(count + 1); console.log(count); // Logs old value. Update is queued, not applied yet. }; ``` **Stale closure when calling the setter multiple times:** ```jsx // Both calls read the same render-time count const handleClick = () => { setCount(count + 1); // count=0, queues: set to 1 setCount(count + 1); // count=0, queues: set to 1 again // Result: 1, not 2 }; // Fix: functional updates chain correctly const handleClick = () => { setCount(c => c + 1); // c=0, result: 1 setCount(c => c + 1); // c=1, result: 2 }; ``` **Shallow copying nested objects:** ```jsx const [user, setUser] = useState({ name: 'Alex', settings: { theme: 'light' } }); // Wrong: settings still points to the original object user.settings.theme = 'dark'; setUser(user); // same reference, React skips re-render // Right: copy every level you change setUser(u => ({ ...u, settings: { ...u.settings, theme: 'dark' } })); // Or split into separate states when values are unrelated: const [theme, setTheme] = useState('light'); ``` **Calling the setter during render:** ```jsx function Bad() { const [x, setX] = useState(0); setX(1); // Infinite loop: setter triggers render, render calls setter again // Fix: move to useEffect or an event handler } ``` One pattern I've seen trip up even experienced developers: calling `setState` inside a `forEach` that loops over the current state. Each iteration captures the same stale array, so only the last update survives. The fix is always the functional form, or building the full next state before calling the setter once. ### Real-world usage - React TodoMVC pattern - `useState([])` for the task list, a separate `useState` for the active filter value. - Next.js forms - `useState({})` for controlled inputs, validated on submit. - Theme switcher - `useState('light')` synced to `localStorage` on every change. - React DevTools - tracks component state internally using multiple `useState` hooks per component. - Remix client-side validation - `useState` holds error messages before the form posts to the server. ### Follow-up questions **Q:** What happens if you call the setter during render? **A:** React throws "Too many re-renders." The setter triggers a re-render, which calls the setter again in an infinite loop. Move state updates to event handlers or `useEffect`. **Q:** What is lazy initialization and when should you use it? **A:** Passing a function to `useState` instead of a value. React calls it once on mount and ignores it on subsequent renders. Use it when computing the initial value is expensive, like reading `localStorage` or parsing a large dataset. **Q:** What is the difference between `setState(value)` and `setState(fn)`? **A:** `setState(value)` captures the value from the current render closure. If multiple setters batch together, they all read the same stale value. `setState(fn)` receives the latest value from the update queue as an argument, so chained updates work correctly. **Q:** How does React 18 auto-batching change useState behavior? **A:** Before React 18, only updates inside synthetic event handlers were batched. React 18 batches updates in `setTimeout`, `Promise.then`, and native event listeners too, reducing unnecessary re-renders. Code that relied on getting a separate re-render per async setter call may need adjustment. **Q:** (Senior) Why must hooks always be called in the same order? **A:** React tracks each `useState` by its position in a per-fiber linked list. If the order shifts between renders (a hook inside an `if`), the index mismatches and React reads the wrong state from the wrong slot. The hooks linter catches this at the tooling level. **Q:** (Senior) If you call `setCount` with the same value as current state, does React re-render? **A:** No. React uses `Object.is` to compare the new value to the current one. If they match, React bails out and skips the re-render entirely. This is a built-in optimization, not something you need to add manually. ## Examples ### Basic: show and hide a modal ```jsx import { useState } from 'react'; function Modal() { const [isOpen, setIsOpen] = useState(false); return ( <div> <button onClick={() => setIsOpen(o => !o)}> {isOpen ? 'Close' : 'Open'} modal </button> {isOpen && <div className="modal">Modal content here</div>} </div> ); } ``` `isOpen` starts as `false`. The button flips it with a functional update. React re-renders and conditionally renders the modal div. No external state management needed for something this local. ### Intermediate: controlled login form ```jsx import { useState } from 'react'; function LoginForm() { const [form, setForm] = useState({ email: '', password: '' }); const [error, setError] = useState(''); const handleChange = (e) => { const { name, value } = e.target; setForm(f => ({ ...f, [name]: value })); // merge one field, keep the rest }; const handleSubmit = (e) => { e.preventDefault(); if (!form.email.includes('@')) { setError('Enter a valid email'); return; } setError(''); // send form data to API }; return ( <form onSubmit={handleSubmit}> <input name="email" value={form.email} onChange={handleChange} /> <input name="password" type="password" value={form.password} onChange={handleChange} /> {error && <p style={{ color: 'red' }}>{error}</p>} <button type="submit">Login</button> </form> ); } ``` Two separate `useState` calls handle different concerns: one for field values, one for error messages. The functional update in `handleChange` spreads the previous form and overrides only the changed field, which avoids the shallow copy trap on nested state. ### Advanced: stale closure with React 18 batching ```jsx import { useState } from 'react'; function BadCounter() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); // count=0, queues: set to 1 setCount(count + 1); // count=0, same closure, queues: set to 1 again // After click: count = 1, not 2 }; return <button onClick={handleClick}>Bad: {count}</button>; } function GoodCounter() { const [count, setCount] = useState(0); const handleClick = () => { setCount(c => c + 1); // c=0, queues: 1 setCount(c => c + 1); // c=1, queues: 2 // After click: count = 2 }; return <button onClick={handleClick}>Good: {count}</button>; } ``` `BadCounter` uses `count` directly in both calls. React 18 batches them into one re-render, but both closures still capture the same render-time `count` (which is `0`). The update queue ends up with two identical instructions: "set to 1." Result: `1`. `GoodCounter` uses functional updates. React passes the latest queued value into each callback, so they chain: `0 -> 1 -> 2`. This is the safe pattern for any handler that calls the same setter more than once.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.