Everything You Need to Know About React useState
Everything you will ever need to know about React’s useState hook, explained with a simple React project.
Checked a few weeks ago what is the percentage of front-end library usage amongst my colleagues around the world. For the first time in a few years, I had the pleasant surprise to see that finally jQuery has been overthrown by a modern JS library, and the king is React 👑.
Based on these statistics, React is preferred by more than 40% of Front-End devs. That’s awesome 🕺 🎊!
As of the time of writing this article, unfortunately, I couldn’t find any statistics relevant enough to see how many React projects are developed in the old fashioned way, based on class components, how many are a mix, between classes and function components, and only function components with hooks. If I would have to guess, I would choose the mixt approach, since most projects having already a few years, would have to migrate from classes to functions if the team wants to adopt hooks.
I’ve decided to create a series of React hooks articles, that can help current React devs to easily migrate their legacy code to modern React, and also to help new coming devs and tech passionates to understand React and hooks.
In this article, we are going to get a deep dive into React’s useState
.
What is useState?
useState
is a React hook.
A Hook is a special function that lets you “hook into” React features. For example, useState
is a Hook that lets you add React state to function components. [Source]
When do you need useState?
You need this hook, every time you are writing a functional component, and you must define some state variables into that component.
What are we building?
We are going to build a Voting Card App. I know it is a basic app, but our purpose is to understand all useState
capabilities and concepts.
I prepared for you a React app wired with TypeScript and Tailwind CSS.
You can clone this repo so we can get it started 😄:
Just cd where/you/cloned/the/project
and run npm i
. Once the package installation is complete, run npm start
and your app will be up and running.
Project structure and technologies
First, let me give you a blazing fast walkthrough of the project. It is a standard CRA project, with TypeScript preset and Tailwind CSS.
Why do I like this structure?
- it is pretty common in most projects
- it is more than enough for our current example
- it has all our working files on the same depth level and in the same place
Why did I choose this setup of technologies?
- I consider TypeScript a must in any modern web app, regardless of team size or app size, because it helps you a lot in terms of bug prevention, sharing data types, and enforcing some well-needed sets of rules
- Tailwind CSS is a utility-first CSS framework. What does that mean? Well, it has lots of utility classes that you can use, instead of writing classic CSS. Once you get used to it, it becomes really helpful.
Let’s start coding 🚀 💻
So first of all, let’s define our data types. Let’s create a VotingUtils.ts
file under our src
folder. Here we will store everything in terms of data types and utility functions if needed.
Our next step is to create our voting card component. Under src
folder, let’s create a file called VotingCard.tsx
:
Now let’s go to our App.tsx
and let’s change it with the following code:
What we’ve done so far is to set up our app and display our voting topics. But there is more than that, we already have a reusable component for all our cards, the data model that we are going to use, and a mocked data constant wired together 😄.
Now let’s add our voting icons. Let’s install react-feather
:
npm i react-feather
This will provide our app with a set of open source icons, that we can use to vote our topics 👍.
Let’s editVotingCard
component and add voting buttons:
If everything went fine until here, our app should look like this:
Finally, we’ve reached the interesting part. We are going to use React useState
hook to create 2 state variables, isLiked
and isDisliked
. This is how we will be able to determine if any card has been voted or not.
We have imported useState
from react
and used it above. Why do we need this? Well because we need somehow to determine, in VotingCard component, if the card is LIKED or DISLIKED.
useState
hook returns a pair of values. The first value will be the state and the second one is a Dispatch function that updates the value. What does that mean? It means that isLiked
and isDisliked
, represent the current state value, and setIsLiked
together with setIsDisliked
, are the functions to update them.
Since we want to store 2 variables every time we are using useState
, we can write this elegantly, with array destructuring.
Ultimately, the correct way to update our state variables is through calling their corresponding functions and passing them a new value.
Now let’s explain a bit what useState<boolean>(false)
does. By appending <boolean>
after useState, we are specifically telling TypeScript, that our state value is of type boolean. And the value passed in useState
as a parameter, in our case false
, represents the initial value that the state variables will have once our VotingCard component is rendered.
With the above in mind let’s create two functions inside our VotingCard
component, one for LIKE and one for DISLIKE, attach them to our buttons, and also add an active color state on our buttons:
Now if we go into our browser and test this, we should be able to LIKE or DISLIKE voting cards.
We have added 2 new functions. Let’s break it down a bit:
The first function onLikeClicked
, will set our state variable isLiked
, to true
, and also it will set isDisliked
variable to false, regardless of its value.
The second follows almost the same behavior, but this time, we just swap the roles between isLiked
and isDisliked
variables.
Let’s jump into our JSX syntax and see what we’ve updated there:
We have added an onClick
handler on both icons, and also, we are taking into account if any of the corresponding state variables is active, we want to highlight them both with blue or red.
An important thing to notice is that reading state in our JSX is as simple as adding our state variables into curly braces { isLiked } / { isDisliked }
.
For the ones who are familiar with the class syntax, everything we’ve done so far is equivalent to the following code:
The setState callback
One thing that we can easily spot in our code so far, is that when we are trying to click multiple times on the same action (LIKE or DISLIKE buttons), the state won’t change. That’s because we are always setting that clicked button to true
.
Well, before creating ifs
into your onClick
functions, let me tell you that, we can handle this scenario easily with our setState
function callbacks.
So far, we said that setIsLiked
and setIsDisliked
accept a value as a parameter, but that is only half true. It also accepts a function as a parameter. That function will take the previous state value, and must return a new state value. Let’s see how this works:
We have changed our functions, that every time we click on onLikeClicked
, to take into account the previous isLiked
, value and negate it.
Same for onDislikeClicked
function, and isDisliked
state variable.
The setState re-rendering
One of the most important aspects of using the setState callback provided by useState
, is that every time any setState callback is called, it will enqueue a re-render operation on that component. Through re-renders, React can update your state and transform UI elements on the screen.
In our case, every time we are calling setIsLiked
or setIsDisliked
, a re-render operation will be executed for our VotingCard component at a certain point in time. This operation queue is managed by React.
Another important thing is that, during multiple state updates, the first value returned by useState
will always be the most recent state after applying updates.
The setState function identity
One thing worth mentioning is that React is guaranteeing that setState functions will have the same identity between re-renders.
What this means, is that behind the scene, the functions will point to the same reference between re-renders, and won’t be detected as a change.
Other functions or variables defined in that component, will point through different references between re-renders. We have a few exceptions with the help of React, but we will learn about them later on.
The lazy initial state
We covered the fact that useState
receives a parameter that will represent the initial value for that state variable, but it can also receive a function as a parameter.
Usually, you want to pass a function as a callback and initialize the state with it, when you have heavy operations to perform.
const [likes, setLikes] = useState(() => {
return someHeavyComputationFunction()})// or depending on how you write the function
// you can also write it something like thisconst [likes, setLikes] = useState(someHeavyComputationFunction)
In this scenario is usually better to just use the lazy initial state pattern and pass a function into useState, that will return your computed state only once.
Remember: Calling the function as a param will result in the same behaviour as passing a value, and it will call that function between re-renders.
// bad
const [likes, setLikes] = useState(computeHugeAmountOfLikes())// good
const [likes, setLikes] = useState(computeHugeAmountOfLikes)
setState updates to the rescue
Why I am saying that setState is rescuing us? It is pretty optimized, and will only update states that have different values than before. In other words, if you are trying to update the current state, with the same value, React will bail out, without rendering the children or firing effects.
Conclusion
We have learned a lot throughout this article. Let’s review everything one more time:
- we created a Voting app
- we added Tailwind CSS and TypeScript and used them throughout this article
- we learned what useState is and all its secrets
This seems trivial, but trust me, you will use useState
in your React journey very often, and you will have to fully understand all its quirks and secrets in order to master React and hooks.
I hope you found this article useful! In case you have any questions or suggestions, please shoot them and I will engage asap.
Stay safe and see you at the next one 🙌!
More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter and LinkedIn. Join our community Discord.