React Hooks
useState
Web Development
JavaScript
Coding Issues

The useState set method is not reflecting a change immediately

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A useState setter in React does not synchronously mutate the current render’s variable. Instead, it schedules a state update and React applies that update during a later render. Once you understand that each render sees its own snapshot of state, the "not updating immediately" behavior becomes predictable.

Why the Value Looks Stale Right After setState

Inside a render, count is just the value for that render. Calling setCount does not rewrite the local variable you already closed over. It tells React to render again with a new state value.

jsx
1import { useState } from "react";
2
3export default function Counter() {
4  const [count, setCount] = useState(0);
5
6  function handleClick() {
7    setCount(count + 1);
8    console.log(count); // logs the old value for this render
9  }
10
11  return <button onClick={handleClick}>{count}</button>;
12}

That log statement is not evidence that React ignored your update. It only shows that the current event handler still sees the state snapshot from the render that created it.

Use Functional Updates When the Next State Depends on the Previous One

The most common bug appears when multiple updates depend on the current value. If you call setCount(count + 1) several times in one event, each call may use the same stale count. The fix is a functional update.

jsx
1import { useState } from "react";
2
3export default function Counter() {
4  const [count, setCount] = useState(0);
5
6  function incrementThreeTimes() {
7    setCount(prev => prev + 1);
8    setCount(prev => prev + 1);
9    setCount(prev => prev + 1);
10  }
11
12  return <button onClick={incrementThreeTimes}>{count}</button>;
13}

Here, each update receives the latest queued value rather than the stale value from the surrounding closure. That is the correct pattern whenever the next state is derived from the previous state.

Run Follow-Up Logic After the Render Commits

If you need to react to the updated state, do that after React has rendered with the new value. useEffect is the standard way to run side effects based on state changes.

jsx
1import { useEffect, useState } from "react";
2
3export default function Counter() {
4  const [count, setCount] = useState(0);
5
6  useEffect(() => {
7    console.log("Updated count:", count);
8  }, [count]);
9
10  return <button onClick={() => setCount(prev => prev + 1)}>{count}</button>;
11}

This is a better place for logging, analytics, storage writes, or network calls triggered by a successful state change. It aligns your effect with the render that actually contains the updated value.

Compute the Next Value Locally When You Already Know It

Sometimes you do not need to wait for React at all. If you are calculating the next value inside an event handler and only need that value for immediate logic, store it in a local variable.

jsx
1function handleClick() {
2  const nextCount = count + 1;
3  setCount(nextCount);
4  console.log(nextCount); // immediate access to the intended next value
5}

This does not bypass React. It simply avoids confusing the current render snapshot with the value you are about to request.

Why Batching Makes This More Visible

React may batch multiple state updates for performance. That means several setter calls can be grouped before React commits the next render. Batching is one reason immediate reads feel surprising at first, but it is part of how React keeps UI updates efficient.

The practical lesson is simple: setters request a future render. They do not mutate the current render in place.

Common Pitfalls

  • Expecting console.log right after a setter call to show the next render’s state.
  • Using setCount(count + 1) repeatedly when a functional update is required.
  • Running side effects inside event handlers when they really depend on committed state.
  • Treating React state like a mutable local variable instead of a render snapshot.
  • Blaming batching when the real issue is stale closure logic.

Summary

  • useState setters schedule updates; they do not synchronously rewrite the current render.
  • Each render sees its own snapshot of state values.
  • Use functional updates when the next state depends on the previous state.
  • Use useEffect for logic that should run after React commits the updated state.
  • If you already know the next value, compute it locally instead of expecting immediate state mutation.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.