Decoding The React useState hook
vedanth bora
Posted on June 9, 2022
What is useState ?
useState
is a React Hook that lets you add a state variable to your component.
const [state, setState] = useState(initialState)
Adding state to a component
Call useState
at the top level of your component to declare one or more state variables.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(21);
const [name, setName] = useState('vedanth');
// ...
The convention is to name state variables like [something, setSomething]
using array destructuring.
useState
returns an array with exactly two items:
- The current state of this state variable, initially set to the initial state you provided.
- The
set
function that lets you change it to any other value in response to interaction.
To update what’s on the screen, call the set
function with some next state:
function handleClick() {
setName('Batman');
}
React will store the next state, render your component again with the new values, and update the UI.
What are state variables ?
Components often need to change what’s on the screen as a result of an interaction. Like typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart and so on.
Components need to “remember” things: the current input value, the current image, the shopping cart and so on. In React, this kind of component-specific memory is called state.
So what is the difference between a regular variable and a state variable ?
In React
- Local variables don’t persist between renders. When React renders this component a second time, it renders it from scratch—it doesn’t consider any changes to the local variables.
- Changes to local variables won’t trigger renders. React doesn’t realize it needs to render the component again with the new data.
So if we try to change something on the UI by changing a regular variable, react will not trigger a render and so nothing will change on the screen.
To update a component with new data, two things need to happen:
- Retain the data between renders.
- Trigger React to render the component with new data (re-rendering).
The [useState](https://beta.reactjs.org/apis/usestate)
Hook provides those two things:
- A state variable to retain the data between renders.
- A state setter function to update the variable and trigger React to render the component again.
Lets try to understand this with an example to understand better.
This is counter and we are trying to update the count with a regular value
import React from "react"
export default function App() {
let count = 0;
function handleClick() {
count = count + 1;
}
return (
<>
<h1> {count} <h1>
<button onClick={handleClick}>
increase count
</button>
</>
);
}
In the example above, React will not trigger a re-render an so nothing will change on the UI.
To solve this we need to use a state variable,
import React, { useState } from "react"
export default function App() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<>
<h1> {count} <h1>
<button onClick={handleClick}>
increase count
</button>
</>
);
}
React will store the next state, render your component again with the new values, and update the UI.
💡 Calling the set
function does not change the current state in the already executing code:
function handleClick() {
setCount(count + 1);
console.log(count) // still 0
}
It only affects what useState
will return starting from the next render.
A few examples using useState
- Counter
In this example, the count
state variable holds a number. Clicking the button increments it.
https://stackblitz.com/edit/react-ts-fxpjaa?embed=1&file=App.tsx
- Text field
In this example, the text
state variable holds a string. When you type, handleChange
reads the latest input value from the browser input DOM element, and calls setText
to update the state.
https://stackblitz.com/edit/react-ts-tpwd62?embed=1&file=App.tsx
- Checkbox
In this example, the liked
state variable holds a boolean. When you click the input, setLiked
updates the liked
state variable with whether the browser checkbox input is checked. The liked
variable is used to render the text below the checkbox.
https://stackblitz.com/edit/react-ts-7fw6wv?embed=1&file=App.tsx
How to u*pdate state based on the previous state*
Suppose the count is 10
. This handler calls setCount(count + 1)
three times:
function handleClick() {
setCount(count + 1); // setCount(10 + 1)
setCount(count + 1); // setCount(10 + 1)
setCount(count + 1); // setCount(10 + 1)
}
However, after one click, count
will only be 11
rather than 13
! This is because calling the set
function does not update the count
state variable in the already running code. So each setCount(count + 1)
call becomes setCount(11)
.
To solve this problem, you may pass an *updater function* to setCount
instead of the next state:
function handleClick() {
setCount(c => c + 1); // setCount(10 => 11)
setCount(c => c + 1); // setCount(11 => 12)
setCount(c => c + 1); // setCount(12 => 13)
}
Here, c => c + 1
is your updater function. A function that calculates the next state based on the previous one in the queue.
It is a way to tell React to “do something with the state value” instead of just replacing it.
React puts your updater functions in a queue. Then, during the next render, it will call them in the same order:
-
c => c + 1
will receive10
as the pending state and return11
as the next state. -
c => c + 1
will receive11
as the pending state and return12
as the next state. -
c => c + 1
will receive12
as the pending state and return13
as the next state.
There are no other queued updates, so React will store 13
as the current state in the end.
By convention, it’s common to name the pending state argument for the first letter of the state variable name, like c
for count
. However, you may also call it like prevCount
or something else that you find clearer.
What about this event handler? What do you think number
will be in the next render?
const [number, setNumber] = useState(0);
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>
// Yes you're right , number will be 6
💡 React may call your updaters twice in development to verify that they are pure.
How to update objects and arrays in state
You can put objects and arrays into state. In React, state is considered read-only, so you should replace it rather than mutate your existing objects. For example, if you have a form
object in state, don’t update it like this:
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Vedanth';
Instead, replace the whole object by creating a new one:
// ✅ Replace state with a new object
setForm({
...form,
firstName: 'Vedanth'
});
What’s a mutation?
You can store any kind of JavaScript value in state.
const [x, setX] = useState(0);
If you’ve working with numbers, strings, and booleans. These kinds of JavaScript values are “immutable,” meaning unchangeable or “read-only.” You can trigger a re-render to replace a value:
setX(5);
The x
state changed from 0
to 5
, but the number 0
itself did not change. It’s not possible to make any changes to the built-in primitive values like numbers, strings, and booleans in JavaScript.
Now consider an object in state:
const [position, setPosition] = useState({ x: 0, y: 0 });
Technically, it is possible to change the contents of the object itself. This is called a mutation:
position.x = 5;
However, although objects in React state are technically mutable, you should treat them as if they were immutable—like numbers, booleans, and strings. Instead of mutating them, you should always replace them.
In other words, you should treat any JavaScript object that you put into state as read-only.
Lets try to understand this with some examples
This example holds an object in state to represent the current pointer position. The red dot is supposed to move when you touch or move the cursor over the preview area.
https://stackblitz.com/edit/react-ts-tmrc2q?embed=1&file=App.tsx
Using a single event handler for multiple fields
https://stackblitz.com/edit/react-ts-crzvrd?embed=1&file=App.tsx
Consider a nested object structure like this:
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
If you wanted to update person.artwork.city
, it’s clear how to do it with mutation:
person.artwork.city = 'New Delhi';
But in React, you treat state as immutable! In order to change city
,
setPerson({
...person, // Copy other fields
artwork: { // but replace the artwork
...person.artwork, // with the same one
city: 'New Delhi' // but in New Delhi!
}
});
How to u*pdate arrays without mutation*
In JavaScript, arrays are just another kind of object. Like with objects, you should treat arrays in React state as read-only. This means that you shouldn’t reassign items inside an array like arr[0] = 'bird'
, and you also shouldn’t use methods that mutate the array, such as push()
and pop()
.
Instead, every time you want to update an array, you’ll want to pass a new array to your state setting function. To do that, you can create a new array from the original array in your state by calling its non-mutating methods like filter()
and map()
. Then you can set your state to the resulting new array.
Lets understand with a few examples.
- Adding to an array
push()
will mutate an array, which you don’t want:
setArtists( // Replace the state
[ // with a new array
...artists, // that contains all the old items
{ id: nextId++, name: name } // and one new item at the end
]
);
setArtists([
{ id: nextId++, name: name },
...artists // Put old items at the end
]);
- Removing from an array
The easiest way to remove an item from an array is to filter it out. In other words, you will produce a new array that will not contain that item. To do this, use the filter
method, for example:
setArtists(
artists.filter(a => a.id !== artist.id)
);
I hope this blog helped you understand useState hook better.
Posted on June 9, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 16, 2024