Hello, I am a front-end developer and I want to share my opinion regarding why you shouldn’t always use useState
.
TL;DR:
useState
is an asynchronous hook and it doesn’t change the state immediately, it has to wait for the component to re-render.useRef
is a synchronous hook that updates the state immediately and persists its value through the component’s lifecycle, but it doesn’t trigger a re-render.
Recently, I was pair-programming with a co-worker who was struggling with a “weird bug”.
Edit: It is not a bug, just misuse of useState.
To put things into context, I made this example.
My tender-hearted friend was confused why his form wasn’t submitting the input correctly as expected.
I suggested that he use a “normal variable” instead of useState
, but he looked down on me as if I was just an idiot (how did he know?😅) and jokingly said: “You obviously don’t know what you are speaking about.”
To his own surprise, the variable came in handy.
Why I love useRef?
Okay, I just want to declare that you shouldn’t always rely on useRef
or a “normal variable” for all cases, but I just want to share why sometimes I would prefer them over useState
.
Edit: I did receive feedback from my readers that my example was incorrect, thus I did update them to illustrate my point.
1- useState
Look at the example below:
Do you know what is going to happen?
useState
is an asynchronous hook, it will wait for the component to finish its cycle, re-render, and then it will update the state. Thus, userToken
line 20 is still an empty string.
2- useRef
When I need to do something immediately and I need it for the flow of my code, then I aim for useRef
…Why? because it has the same power of persistence that useState
has.
It sustains the value through the component’s life cycle, but the cool part is that it is synchronous!
Let’s re-write the example above and make it work this time!
What is cool about this, is that it will stay the same throughout the component’s life cycle and won’t be initialized to false no matter how many times the component re-renders.
useRef
is only intended to persist the state and update synchronously, however, it won’t trigger a re-render. So, don’t replace useState
with useRef
everywhere.
3- normal variable
In my case, I don’t need to sustain the value of userToken
because I will only use it once, thus I can replace useRef
with a normal variable.
How useState works
I made an illustration code to explain how theuseState
hook works under the hood.
If you have ever heard of closures, then this will look familiar.
A “Closure” by definition means that a function can access the scope where it was initially created, even if it was being executed outside of it.
In this case, state
and setState
are functions that are being executed outside of their scope, but they can still access the scope where they were initially declared inside.
As you can see, setState
is asynchronous and it has to queue an order for a component re-render, wait for this order to be fulfilled, then it will update the state.
How does it keep persist the state value throughout component re-renders? closures, my friend.
Closures give you the power to create functions with a “memory” that persist, which means you execute the function again it will have reference to the previous execution…Let me explain:
const playGame = () => {
let counter = 0;
const increment = () => {
if (counter === 9) {
console.log("Don't you have something better to do?");
return;
}
counter += 1;
console.log(counter);
};
return increment;
};
const onClick = playGame();
onClick();
onClick();
onClick();
onClick();
onClick();
onClick();
onClick();
onClick();
onClick();
onClick();
Why don’t you try this code in the console?
Guess what happened here?
Exactly, closure keeps a “cache” or a “memory” of the function’s scope that the function can access even if it is being executed outside.
Conclusion
Don’t look down on someone and tell them “You obviously don’t know what you are speaking about.” or that person will write a blog about it.
Thanks for reading.