DEV Community

Cover image for Accessing React State right after setting it
Sung M. Kim
Sung M. Kim

Posted on • Originally published at slightedgecoder.com on

Accessing React State right after setting it

Photo by Celso on Unsplash

As I have been browsing Stack Overflow questions, I’ve noticed that many bugs are due to trying to access a state value after setting it.

An example question on Stack Overflow.

I’ve stumbled many times for being unaware of setState being an asynchronous operation.

How do we access the state value right after setting it then?

😬 Reproducing the Problem

Here is the code that shows accessing a state value (clickCounts) right after setting it synchronously.

And let’s see the logical error.

console.log doesn’t have access to updated state value even though the call is made after setState.

😒 Workaround (Not Recommended)

As setState is an operation, you can just wait till the value is set by React.

You might wait for a certain period to access the updated state using
setTimeout.

Tada 🎉. It works right?

Yes but No, at this point, you are just praying 🙏) that setState finishes before accessing the state within setTimeout.

And also, you need to persist the event to be able to access event argument as shown in line#2 (e.persist()).

Refer to Event Pooling for e.persist.

😄 Recommend Ways

There are two ways as mentioned in the official React documentation.

  1. Using a callback passed to setState.
  2. Using componentDidUpdate life cycle method

Let’s go over them both.

1. Using a callback passed to setState

setState has the following signature.

The callback is called after the state has updated using updater method thus the callback has access to the updated this.state.

Here is the updated code & the demo.

2. Using componentDidUpdate life cycle method

React documentation “generally recommends” using componentDidUpdate.

I haven’t been able to find the reason for it, but my guess is because componentDidUpdate has access to the previous props and previous state (as well as being called before the callback as my demo shows).

Here is the code using componentDidUpdate.

And this demo shows that componentDidUpdate

  1. has the access to the updated state value.
  2. is called before the setState’s callback method.

👋 Parting Words

Frankly speaking, I’ve only used the callback to access updated value because I only found out about the recommended way of using componentDidUpdate while writing this blog 😝).

And you can play around with the demo on CodeSandBox.

The post Accessing React State right after setting it appeared first on Sung's Technical Blog.

Top comments (34)

Collapse
 
andiekl profile image
AndieKL

Thanks for the article! I just had this problem yesterday. It took a while to figure out that the problem was setState and not the helper function I was running just ahead of it but I eventually realized my error. I used a callback instead of the lifecycle method because it was all part of a form submit. Hmmm, I just realized there's a better way... Off to improve my code!

Collapse
 
dance2die profile image
Sung M. Kim

You're welcome there Andie.

But be aware of componentDidUpdate causing an infinite recursion as Truong pointed out.

You can read it in the reactjs documentation, it says:

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.

The example is

componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}
Enter fullscreen mode Exit fullscreen mode

In my case, i update and re-render component whenever i receive new data from websocket.

Collapse
 
anurbol profile image
Nurbol Alpysbayev • Edited

React's state management is so cumbersome (including redux). I am making an open source state management (for React, Angular, Vue) that will be as easy as possible for newbies (and includes cool stuff like very simple observables for others). Follow me on twitter if you want to know about that in a month (or two) twitter.com/not_borats_code

Collapse
 
dance2die profile image
Sung M. Kim

I've started using MirrorJS, and I found it to be quite easy to get started with.

MirrorJS looks like something one with an extensive experience with Redux. Kind of like how I might end up organizing redux code.

Collapse
 
anurbol profile image
Nurbol Alpysbayev • Edited

Seems like MirrorJS while having good intentions and is overall a good thing, sacrifices features to simplicity (quote from their readme: "define routes without caring about history"). Also if it does not have compatibility with react, I doubt it has a bright future.

P.S. I've looked at your profile, if you have .NET background, you might want to look to angular instead of react. The former is a more solid base for new, big-scaled project, the latter is good for small to medium projects, or for existing projects with legacy (in any language).

Thread Thread
 
dance2die profile image
Sung M. Kim

Thanks Nurbol for the tip on Angular. I will consider it over Vue for the next framework.

if it does not have compatibility with react

Might ask what you meant by the compatibility issue?

Thread Thread
 
anurbol profile image
Nurbol Alpysbayev • Edited

Vue is also good, but it is in the same basket with React, unlike Angular. By compatibility I meant the same API. If MirrorJS does not compatible with React, all the libraries and entire ecosystem for React is useless for MirrorJS. I just didn't look if it's compatible, so I don't know if it's true.

Thread Thread
 
dance2die profile image
Sung M. Kim

I see that MirrorJS has a devdependency on "react": "^16.3.2", so if React v.17 is release it'd not be compatible.

Collapse
 
kayis profile image
K

It's kinda strange that the setState() method doesn't return a promise.

Collapse
 
theodesp profile image
Theofanis Despoudis

React uses a callback style programming mostly as promises are still difficult to grasp and a little bit more expensive.

Collapse
 
kayis profile image
K

True, but in React-Native examples they also use promises and async functions.

Collapse
 
rajatkantinandi profile image
Rajat Kanti Nandi

I faced similar problem implementing react native version of a 2048 game variation & used settimeout of 100ms & get rid of the problem & later figured out that setState is async & modified the code to use the callback.

Collapse
 
dance2die profile image
Sung M. Kim

It seems like you, & I (and this other person) has a similar thought process 😅.

Collapse
 
kevinejiro profile image
Ogidigbo Ejiro

lol me too !!!

Collapse
 
revskill10 profile image
Truong Hoang Dung

Don't forget to use shouldComponentUpdate, because you're gonna make an infinite recursion in your componentDidUpdate

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the tip there Truong.

Would you share a case how an infinite recursion could occur?

Collapse
 
revskill10 profile image
Truong Hoang Dung • Edited

You can read it in the reactjs documentation, it says:

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.

The example is

componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

In my case, i update and re-render component whenever i receive new data from websocket.

Thread Thread
 
dance2die profile image
Sung M. Kim

it must be wrapped in a condition

Ah without it, a component definitely state to update infinitely.

Thanks Truong.

Collapse
 
juanfrank77 profile image
Juan F Gonzalez

Funny thing is, I haven't ran across that error yet because I normally use componentDidUpdate for those state changes. Tutorials I followed taught my well I guess... hahaha. +10pts for that online editor you're using :p

Collapse
 
theodesp profile image
Theofanis Despoudis

Sometimes state changes from higher order components or from implicit state changes so you use componentDidUpdate to properly catch the new state and perform some additional logic.

Collapse
 
dukemai profile image
dukemai

interesting post. My favorite solution is to use requestAnimationFrame for the block that want to receive the new state. I usually limit usage of componentDidUpdate in this case since it can cause forever loop if there is any state update in componentDidUpdate

Collapse
 
bgadrian profile image
Adrian B.G. • Edited

3 . Keep your data in a Store/model, you have the latest data anytime while keeping the "single source of truth"

Collapse
 
dance2die profile image
Sung M. Kim

Hi @bgadrian , did you mean using libraries such as Redux/MobX?

Collapse
 
bgadrian profile image
Adrian B.G.

That would be one way yes.

The idea is that if you need a state object and the React way is a problem you can move it to a model. I would not recommend it for something simple like your examples of course, just stating that this possibility exists.

Thread Thread
 
dance2die profile image
Sung M. Kim

That sounds like a spot-on idea.

I recently created a small web page and used MirrorJS (uses Redux underneath) to keep all states in one place.

setState wasn't needed in most of places and all the states were managed in one place.

Collapse
 
thebuildguy profile image
Tulsi Prasad

I have no words to thank you for this. I am a beginner in React and literally due to this particular issue, I was stuck for almost half a day. I used to think stuff involving API calls would be async, but I suppose there's more than just that. After all, I came to know this now. Great article! 🦄🚀

Collapse
 
andrenm profile image
André

I create an account just to say thank you. The callback from the sestate worked for me.

Collapse
 
dance2die profile image
Sung M. Kim • Edited

Welcome to DEV André 👋.

You're welcome & thank you for taking time for the reply, as it means much to me :)

As aside note, using hooks, a similar way is to accomplish is to use useEffect (as you do for componentDidUpdate).

e.g.)

function App() {
  const [name, setName] = useState(undefined);

  useEffect(() => {
    const fetchName = async () => {
      const remoteName = await (await fetch("...")).json();
      // remoteName => 'André'
      setName(remoteName);
    };

    fetchName();

    // At this point, name is still "undefined", not "André"
    console.log(name);
  }, []);

  useEffect(() => {
    // You can skip your process, when the name is not yet set.
    if (name === undefined) return;

    // At this point, the name is "André"
    console.log(name);
  }, [name]);

  // return ...
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
ddhogan profile image
Donna Hogan

This blog post has saved me like 3 times now :D

Collapse
 
dance2die profile image
Sung M. Kim

Woohoo great to hear that the post was helpful 😄

Let's hope together and edge case like this can be handled without much thought 😎

Collapse
 
khaledyosry_ profile image
Khaled Yosry

I just created an account to thank you for this article.

Collapse
 
dance2die profile image
Sung M. Kim

Thank you, Khaled :)
And welcome to DEV~ 👋

Collapse
 
sargnec profile image
Necmettin Sargın

Thank you! Reaching prevState from componentDidUpdate solved my preoblem.

Collapse
 
doodleragon profile image
Đinh Nguyễn Nhật Tùng

Very helpful!