React - Asynchronous state update and its problems
- Georgios Tsakoumakis
- Jun 29, 2023
- 3 min read
The counter-intuitive and non-evident approach React has taken to update state...

By George Tsakoumakis
29 June 2023
When I first started learning React a couple of weeks ago, I soon started seeing things which seemed counter-intuitive to my way of thinking as a programmer. Usually, in languages like Python or Java, when you have a value and use a setter to change it, the value changes! But that isn't always the case with React. It turns out most beginners face these kind of issues when first beginning with React, and the vets keep giving us the same answer: "Don’t fight against the framework you’re using, lean into it". By and large, they are all saying "it's not a bug, it's a feature", and that just makes my morning coffee taste a little worse every time I hear something of that sort. It slowly eats into my will to live and be a programmer. Not anything new though.
For context, I have been building a drum machine that was supposed to look like this:

The 'Power' switch turns on or off the audio for the pads, and the 'Bank' changes the audio set for the pads into chords. A pretty dumb project overall, but necessary for my certification. Regardless, I have been trying to get the display (gray panel above 'Bank') to show the correct bank set when I switch between 'Heater Kit' and 'Smooth Piano Kit'. Here is the funny part: The display would show the wrong set for the corresponding audio clips. So, if I was playing the chord audio, it would display 'Heater Kit', and if I was playing the drums audio, it would display 'Smooth Piano Kit'. Naturally, I did some console.log debugging. And oh God what did I find...
So right before I set the bank to the other one when flicking the switch, I added a before print statement and then an after. Aaaand they showed this:
Heater Kit before.
Heater Kit after.
Smooth Piano Kit before.
Smooth Piano Kit after.Ummm, what? How does that make sense? But my code is this!
const selectBank = () => { if (power) { // console.log(bank + ' before'); bank === 'Heater Kit' ? setBank('Smooth Piano Kit') : setBank('Heater Kit'); setDisplay('Bank: ' + bank); setTimeout(() => setDisplay(String.fromCharCode(160)), 1000); // console.log(bank + ' after'); } }
That just didn't make any sense to me. But having experienced a similar issue with async requests in the first post, I thought, "oh state might update asynchronously", and after some research I saw I was right. Some people claimed that happens for performance reasons, but that is way to complicated for me to grasp right now. And frankly I have better things to do. Like fixing this issue.
Anyway, here is what it took to solve it:
const didMount = useRef(false); const selectBank = () => { if (power) { console.log(bank + ' before'); setBank(prevBank => prevBank === 'Heater Kit' ? 'Smooth Piano Kit' : 'Heater Kit'); } }; useEffect(() => { // Return early if this is the first render if (!didMount.current) { didMount.current = true; return; } // Code to be executed on subsequent renders console.log('Bank: ' + bank + ' after'); setDisplay('Bank: ' + bank); setTimeout(() => setDisplay(String.fromCharCode(160)), 1000); }, [bank]);
As you see, it is quite a complicated solution. Also careful with the return in the useEffect, you should not return anything that is not null, otherwise useEffect doesn't work.
One small final detail. Even if I say it doesn't run on the first render, it actually does, because it turns out it is the first 2 renders. But honestly I have given up my hopes of solving this and frankly I don't mind much. Also I have more bugs to fix. Surprising right? Well, when I fix them I will make a post about them too. Probably. Until then, farewell.


Comments