LogoTRUONG PHAM
Home
Projects
Portfolio
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
  • Portfolio
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

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

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.

June 10, 2024·3 min read

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.

Previous: Blog #27: useEffect is not the place for doing all synchronizing logicAll posts in this seriesNext: Blog #29: When 'Controlled Component' turns into a performance burden