Sebastian Pieczynski's website and blog
Published on: yyyy.mm.dd
Published on: yyyy.mm.dd
Created with ❤️ by Sebastian Pieczyński © 2023-2024.
Published on: 1/11/2024
Changes to state in React cause a re-render, that means it cannot be changed directly if we want to see changes to it reflected on the screen. Changing state is done by using specialized functions that manipulate internal variables that React manages and updates them for next render.
There are two main hooks used for managing state (useContext
is a bit different beast) in React: useState
and useReducer
hooks. Before hooks state was an object and it was slightly more similar to what reducer representation looks like, but we'll deal with the hooks only today.
From the two useState
has much simpler API layer:
In this case we are importing useState
hook from react to allow a variable (or state) to change over time (between renders). We initialize our count state with value 0
- it allows us to control what the count
variable is when the application starts.
When we invoke the useState
function we can also set initial value for our variable. It is passed as a parameter to the useState
function.
Here we have set the count
variable to initial value of 0
. Setting initial value is optional and you can also use a setter function that will be invoked when state is initialized and never again.
Note that we are not invoking function with useState(computationallyExpensiveInitializationOfState)
but are passing the function to the useState
hook. This way React will invoke it only once.
If we did invoke it it would happen on every render of the component and could be slow down our application for no reason.
To change the state we defined two functions increment
and decrement
that manipulate (mutate or change) the count
variable.
The variable and function can be called whatever you fancy but it is a common pattern to use name
and setName
when destructuring these values from useState
hook.
Try changing the setCount
function to forceCountToBe
like so:
It still works! But why do we need to use this special function?
Couldn't we just update the state directly? Let's try it. Change forceCountToBe
back to setCount
and replace the increment
function with:
and declare the destructured count
and setCount
with let
instead of const
.
And a bit surprisingly it actually kind of works.. if you decrease the variable after incrementing it you will see that it was updated. This happens because React does not track the count
variable changes itself but uses setCount
function to schedule a re-render of the component with new value. And schedule is an important distinction here. Let's go back to the expected state of the world. Change the increment function back to: const increment = () => setCount(count + 1);
and let us explore the useState
hook some more.
Maybe for some reason we need to increment the value twice... how can we do this?
Easy right? Invoke the function twice (that might not have been your first thought but bear with me) like so:
We are telling React that when button is clicked it should run a function that will increment the count twice:
But it does not work, our count
is still incremented by 1.
Let's check what happens and use good old console.log
to check what the value is before and after each increment:
What you get in the console:
Now wait a second, you may say.. NOTHING changed even after the setCount
was called!
This is what I meant that React schedules the updates. The function does not really increment value until next render. If you think about it in a broader scope it will become clear - what if we had 20 invocations to different set...
functions? Should React re-render on every invocation then? You would only care if the whole application was ready and not about partial updates. And that is why React schedules updates instead of changing the value immediately.
This leads us to the next "quirk" as you might see it: how then are we supposed to access the value after the change but before the state is re-rendered? We may need it to compute other things and now it seems we need to run it after the page renders again?
The solution is quite simple...
If React itself is does not expect values of state to change within single frame we need to "escape" it or move aside and use it to our advantage. What if we just created a new variable that holds the new value and used that? Seems straightforward enough so let's see what happens.
Now accessing new count value is easy:
Now suppose we now need to double the value of count only if it's divisible by 15. How would we access the previous value? Turns out that React can instead of new state accept an updater function that will take the currently pending state and return it allowing us to manipulate it's value.
To use the updater function for new state we pass it as an argument to the function in setCount
:
If you are curious how that could affect our previous examples, here's a question: What would happen if we introduced such change to our increment function and invoked it multiple times?
If you look at the result it will show '3' after the third invocation of the increment
function. It is completely different from the previous example.
This is due to the fact that we are now using the updater function to access the pending
or scheduled
state. This has also added benefit that we do not need to use the current count
variable to access the current state and our function is not dependant on it. It is very handy as when used with useEffect it will not need to add count
to the dependency array. I am getting ahead of myself but I do find this part very important so keep in mind that using updater function helps limit the amount of variables we depend on for effects.
What if we have user data we want to store in state? Since we don't really want to separate data into multiple useState
hooks like:
We should use an object to do this:
If we need to update one of the properties we can do it like this:
We are now recreating user object by spreading the prevUserData
object and overwriting the firstName
property with the new value and finally assigning it with setUser
function.
Today we have learnt how useState
hook can be used to manage state in React and how to: