Introduction
State management is a crucial aspect of building React applications. In React, state refers to any data that the application needs to keep track of and update over time. For example, a user's name, email, and contents of shopping carts are all examples of state.
The useState()
is a built-in hook that allows developers to declare and manage state variables in function components.
In this article, we will explore the basics of useState()
, best practices and some common pitfalls.
Setting up useState()
Let's see how to set up the useState()
before we can use it for state management.
Here are the steps to do so:
Import
useState
from 'react':import { useState } from 'react';
Declare a state variable:
const [stateValue, setStateValue] = useState(initialValue);
const [name, setName] = useState("Jhon");
Here, stateValue
is the current state value, setStateValue
is a function to update the state value, and initalValue
represents the initial value that we can set to a valid javascript type: a boolean, string or object.
The useState()
returns an array of exactly two elements:
state value: This represents the current state value.
set function: This allows us to update the state value and trigger a re-render.
It is a convention to name the state variables like [something, setSomething].
Updating State with useState()
Once we've set up useSate
in our component, we can use it to manage state values.
Here's an example of how to use useState()
[something, setSomething]. to update a counter value in response to clicking a button.
import { useState } from 'react'; // import useState[something, setSomething].
function Counter() {
const [count, setCount] = useState(0); // declare counter state
function handleClick() {
setCount(count + 1); // update counter value.
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
In the above example, we declare a count
state variable, with an initial value of 0
, and a state update function called setCount
. In the handleClick
function, we use setCount
to update the count
value by adding 1
.
When the increment button is clicked, the handleClick
updates the count
value using setCount
, and when the state updates, React triggers a re-render of the component to reflect new state values.
Keep in mind that every time we update a state variable, React automatically re-renders the component.
Caveats when updating a state
When updating state variables using the set
function, there are some caveats we should be aware of to avoid unexpected behaviour.
The updated state is not available in the currently executed code block
When you call the set
functions to update the state variable, the state is not immediately updated within the currently executing code block. This means that if you access the state variable immediately after calling the set
, you will still get the previous value of the state.
For example, consider the following code:
Suppose the count value is 0.
function handleClick() {
// initial count value is 0
setCount(count + 1); // increment count
console.log(count); // Still 0
}
In the example, when the handleClick
function executes, it calls setCount()
to update the count
state variable, and then immediately logs the value of the count
to the console.
However, the output of the console.log()
statement will still be 0, even though we just called setCount()
to update the count
value. This is because the new value of count
is not immediately available within the handleClick
function.
Instead, React put updates into a queue
, and trigger a re-render. During the re-rendering phase, React process updates from the queue
and reflects the updated states into UI.
Batch update
In cases when we need to update more than one state variable, React groups multiple state update operations into a single update operation, and this process is called
batch update
.The
batch update
improves performance by reducing the number of re-rendering.
Suppose the count
value is 2. The following handleClick
function calls setCount three times in a row, each time incrementing the count value by one.
function handleClick() {
setCount(count + 1); // setCount(2 + 1)
setCount(count + 1); // setCount(2 + 1)
setCount(count + 1); // setCount(2 + 1)
}
However, React will not update the state immediately nor does it trigger a re-renders for each call to setCount()
. Instead, it groups state updates and performs a single re-render with the updated state value.
To avoid this issue, we can pass a function to the setCount()
instead of a value. This function will receive the previous state value as an argument and return a new state value.
function handleClick() {
setCount(prevCount => prevCount + 1); // setCount(2 => 2+1)
setCount(prevCount => prevCount + 1); // setCount(3 => 3+1)
setCount(prevCount => prevCount + 1); // setCount(4 => 4+1)
}
Here, prevCount => prevCount+1
is called the updater function. It takes the previous state value and calculates the new state value from it.
Treat state as read-only
In React, the state is considered to be read-only, because it should not be modified directly. Instead, we should use the set
function returned by the useState()
to update a state.
Modifying the state variable directly can lead to unpredictable behaviour, as React may not be able to detect the change and trigger a re-render.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
// ๐ฉ Don't modify the count directly:
count = count + 1;
// โ
Use set function to update the count:
setCount(count + 1);
}
// .....
}
Updating objects and arrays in the state
When updating an array or an object, we need to be aware of a few important things.
Updating objects:
As we know that states are read-only, so we should create a new object with updated properties, and replace the state with the new object, instead of modifying the existing one.
For example, suppose the state has a position object with two properties: x
and y
.
const [position, setPosition] = useState({ x: 0, y: 0 });
Now if we want to update the x
, then we need to replace the state with a new object, instead of directly updating x
.
First, we can copy the previous object with the spread operator, and override the properties that we want to modify.
// ๐ฉ Don't modify an object in state like this:
position.x = 5;
// โ
Replace state with a new object
const newObject = {
... position,
x: 5
}
setForm(newObject); // set newObject to state
Updating Arrays in State
Similar to objects, we should not modify arrays in the state directly, instead, we should create a new array with updated values and then set the state to use the new array.
const initialMovies = [
{ id: 0, title: 'Batman', rating: 8 },
{ id: 1, title: 'Superman', rating: 7 },
{ id: 2, title: 'Ironman', rating: 8.2 },
];
const [movies, setMovies] = useState(initialMovies);
Most often we need to perform the following operations on the array state: add, update or delete an item.
We can use array methods like map
and filter
to modify the array because they return a new array without modifying the original array.
Adding an item to the state array:
addMovie(movie){
// create a new array with new movie
const newArray = [...movies, movie];
setMovie(newArray); // Replace state with new array
}
Removing an item from the state array:
deleteMovie(movieId){
// create new array by removing movie
const newArray = movies.filter( movie => movie.id != movieId );
setMovie(newArray); // Replace state with new array
}
Update an item in the state array:
updateMovie(movieId){
const newArray = movies.map( movie => {
if(movie.id === movieId){
// Create a *new* object with changes
return { ...movie, rating: 8.6 }
}else{
// no changes
return movie;
}
});
setMovie(newArray); // Replace state with new array
}
Summary:
To help you remember the most important things about the useState()
, keep these points in mind:
It allows the functional component to manage the state.
It takes an initial state value as an argument and returns an array of two items: the current state value and a function to update the state.
It is important to remember that state updation is asynchronous and that multiple state updates may be batched together for performance reasons.
When updating a state that depends on the previous state, we should use the updater function syntax to avoid unwanted behaviour.
The state is considered to be read-only, we should not modify state variables directly.
We should always use the
set
update function to update state variables.