LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #28: The naivety of believing setState is an immediate assignment
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #28: The naivety of believing setState is an immediate assignment

I once thought calling setCounter(count + 1) meant the line of code right after would get the new value. This mistake cost me dozens of hours debugging 'ghostly' logic.

TP
Truong PhamSoftware Engineer
PublishedJune 10, 2024

I admit I once had a very naive view of useState. In my mind back then, setCount(5) was no different from an assignment count = 5. I believed that immediately after calling that function, the data in the machine would be updated.

I believed it because in traditional programming languages like C++ or Java, once you assign a value, that variable changes immediately. I brought that mindset to React and encountered a serious mishap in a "multi-step order confirmation" feature.

1. Context: When data "froze"

I was building a multi-step form. When the user clicks "Next," I update currentStep and then call another function to fetch data based on that new step.

const handleNext = () => {
  setStep(step + 1);
  console.log(step); // Why does it still show the old value?
  fetchStepData(step); // Sends API for the wrong step, causing serious data errors for the user
};

The result: The whole system was out of sync. I frantically added setTimeout to "wait" for the state to change, or worse, used useEffect just to catch that change. The entire code became a trash heap of patches.

2. Concept: State is a Snapshot

The problem isn't that React is slow, but that State in a single render is immutable. When you call setStep, you aren't changing the value of the existing step variable. You are asking React: "Hey, use the new step on the next render". But the current code is still holding the old "Snapshot" of step.

3. Practical Approach and Balance

A Junior will encounter this at least once a week. They often resolve it by using useEffect to observe the state, making the logic flow fragmented and extremely difficult to follow.

Comparison of two ways:

  • The "Naive" way: Call setState and then use that state right in the next line. Leads to logic bugs.
  • The Practical way (Functional Updates & Variables):
    • If you need the new value immediately to do something else, calculate it as a local variable (const nextStep = step + 1).
    • Or use Functional Update: setCount(prev => prev + 1).
// Sustainable way
const handleNext = () => {
  const nextStep = step + 1; // Calculate the new value
  setStep(nextStep);         // Request React to update later
  fetchStepData(nextStep);   // Use the newly calculated value immediately for other tasks
};

Lesson Learned

I learned that: Never trust the value of a State immediately after you've called its update function.

  • When the old way (careful) is still correct: Almost never. You must always understand that setState is asynchronous from the perspective of the currently executing code.
  • When to avoid: Avoid using State as the "sole source of truth" for logic running immediately after it. Use local variables or function parameters.

Clarity about React's execution flow is the boundary between a coder and an engineer.


Notes on the day I stopped waiting for immediacy from React's promises.

Series • Part 28 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #29: When 'Controlled Component' turns into a performance burden
Blog #27: useEffect is not the place for doing all synchronizing logic
23Blog #23: Multi-tab Syncing – When your application 'talks' between tabs24Blog #24: Failed Optimistic Update – When efforts to 'smooth' UI turn into UX disasters25Blog #25: Context API Perf Hit – When the 'standard React' solution betrays you26Blog #26: Don't let 'Single Source of Truth' become blind dogma27Blog #27: useEffect is not the place for doing all synchronizing logic28Blog #28: The naivety of believing setState is an immediate assignmentReading29Blog #29: When 'Controlled Component' turns into a performance burden30Blog #30: A giant State Object doesn't make your code cleaner31Blog #31: 1 AM and the Search for the 'Missing' File32Blog #32: Friday Afternoon, 800 Lines of Code, and a Miscalculation33Blog #33: 10 PM and the 'Infinite Loop' of Punishment
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles