Actually understanding useState hook

Here is all about the useState hook, and all you need to know to debug your applications…

Sayan Bhattacharya
CodeX

--

hook image

React’s useState hook is the first hook we are taught yet it is the trickiest to learn in my opinion. As far as I see all the useState tutorials and docs just teach us the basic usage of useState but that's not all. Things like:-

  1. How to deal with asynchronous situations?
  2. Is setState synchronous or asynchronous function?

I will try to clarify these things with some examples.

Back to basics

If you don’t know anything about the useState hook, let me quickly show its basic usage.

In the above code, we just declared a state variable, counter .

  • On clicking the “Increment value” button we are setting the value of counter to counter + 1 thus incrementing the value of the variable in every click.
  • Thus the state of the variable changes and the component is re-rendered which allows us to see the updated value in the UI.

Why use state variables then? We can use normal javascript variables?

Ans> No, we cannot. If we use a plain old ‘var’ or ‘let’ javascript variable the re-render will not be triggered since React only sees’s the useState variable state changes.

What is a state change?

Ans> For React a state change happens when prevState !== currentState . So for primitive values(number, boolean, string) that will be just changing their values. Eg:-

const [name, setName] = useState("Me"); // setName("Hamster")
const [marks, setMarks] = useState(0); // setMyNumber(100)
const [working, setWorking] = useState(false); // setWorking(true)

All the above will trigger a state change.

For objects and arrays, you have to pass a new object or array. If you are familiar with immutability then this will not be new for you. If you pass a new object or array then only the condition prevState !== currentState will be triggered. Here is a great resource if you want to learn about immutability with examples. The spread operator will be super useful for this.

const [students, setStudents] = useState(["me"]);
// setSudents([...students, "another student"]);
const [obj, setObj] = useState({something: "hello"});
// setObj({...obj, someMoreThings: "bye"});

All the above will trigger a state change and re-render the component.

Sync or Async?

This was a big misconception for me for a long time. Are “set” methods we used above synchronous or asynchronous? And the answer may surprise you… The methods are synchronous. Oh wait that was not surprising at all BUT wait, there’s a challenge for you.

So what do you think will the console.log(counter) print after the setCounter(counter + 1) is called?

You will see the counter value is the same after incrementing it by 1. So what is happening? The setCounter is supposed to be a synchronous function right? So the change should be reflected immediately in the next line? NOO.

Turns out that React did this deliberately. Since setState functions are really expensive so React batches many setState-s so we cannot depend on the state change for any computation. Here the correct way to print the value will be just to print console.log(counter + 1) .

How to deal with asynchronous situations?

Okay so far we have just known about the basics of useState but there’s more to it and I have not seen this part in many places explained properly.

So what about some asynchronous situations? For example, a callback function or some socket operations? For these cases, the setState will work the same right?

I am afraid that's not the case. Let's consider some examples.

Async counter

Here we have set up a click listener on the document so when you click the page the counter should be incremented by 1. But as you can see, the counter is incremented by 1 the first time but then gets stuck.

Why it's not working?

Basically, for the asynchronous things, React binds the state variable with the initial value that is 0 so the first time the counter + 1 results in 1 but when the screen is clicked another time for that function the counter variable is still 0, thus again counter is set to 1. So to prevent this we need to need the last rendered value and do our calculations with the rendered value.

And here comes the new part, the setState variable can take a function as the parameter too. It will be crystal clear with an example.

The above code is similar to the CodeSandbox code but with lines 8–10 being different. This time we have passed a function to the setCounter function. The parameter of the function is basically the rendered value of the state variable and thus time everything works as expected when the screen is clicked the counter goes up!!!

Conclusion

The above method can be used for sockets or any other asynchronous situations as well. Passing a function to the setState function is not much covered in the tutorials but I think that is a very essential case that is needed many times in an application. Apart from that, I think the reader also got a good understating of the useState hook. Thanks for the read. Hope you have a good day!!! Cheers 😄

--

--

Sayan Bhattacharya
CodeX
Writer for

Trying to learn and help people learn. Full Stack developer, WebRTC enthusiast