React Hooks: useState Explained

ยท

7 min read

React Hooks: useState Explained

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:

  1. state value: This represents the current state value.

  2. 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.


References

ย