Telerik blogs

React Hooks have transformed the way we build and manage state in functional components. By extracting logic from a component, they enable us to write clearer, more intuitive code. In this article, we’ll discuss the most fundamental hook of React—useState.

React Hooks have transformed the way we build and manage state in functional components. By extracting logic from a component, they enable us to write clearer, more intuitive code.

We’ve explored hooks in previous articles with a focus on useEffect, useContext and useReducer. Today, we delve deep into the most fundamental hook of React—useState.

Component State

Before we dive into understanding how the useState() hook can be used, we first need to understand a core concept of React—state.

At the heart of every dynamic React application lies the concept of “state.” State can be thought of as any data that determines the output or appearance of a component and can change over time. It’s what allows our React applications to be interactive and responsive to user inputs or system changes.

Historically, component state was managed within class components using constructs like this.state and this.setState(). However, with the introduction of hooks in React, state can now be managed in a more elegantly and concisely within functional components, with the useState() hook.

useState

The useState() hook allows us to add React state to functional components. The useState() hook takes a single argument which is the initial value of the state property being created and returns an array of two values:

  1. The state property we want to create.
  2. A function used to update the state value.

In programming languages, an array that returns a fixed number of elements whose values are of a known type is often recognized as a “tuple.”

state is the state variable; setState is the function that updates state variable; initialState is the initial value of state variable

Here’s an example of using the useState hook to create a message state property with an initial value of “Hello World!”

const [message] = useState("Hello World!");

From the useState hook, we can also de-structure a function that can be used to change the value of the state property being created. In our use case, we’ll call this function setMessage().

const [message, setMessage] = useState("Hello World!");

We’re returning the first and second items of the array with array destructuring, a feature introduced in ES6.

We’ll display the value of the message state property in our component and also have a <button> that when clicked triggers the setMessage() function.

import React, { useState } from "react";

const HelloWorld = () => {
  const [message, setMessage] = useState("Hello World!");

  return (
    <div>
      <h2>{message}</h2>
      <button onClick={() => setMessage("The message has changed!")}>
        Update message
      </button>
    </div>
  );
};

When the HelloWorld component is first rendered, the “Hello World!” message is shown. When the user clicks the <h2> element, the setMessage() function updates the message state property to a new value of "The message has changed!". If the value of a state property is ever changed, React re-renders the component to show the new state value.

clicking the update message button changes the text from 'hello world!' to 'the message has changed!'

Try the above example in the following CodeSandbox.

Initializing State with a Function

We’ve mentioned that the useState hook takes a single argument which is the initial value of the state property being created. However, sometimes, the initial state value isn’t a simple string or number; it might require some computation. To avoid unnecessary calculations every time our component re-renders, instead of providing a direct value, we can pass a function that returns the desired initial value. This function is only executed once during the component’s initial render.

For example, we can have a function return the value of "Hello World!" as the argument passed into the useState() hook.

const [message, setMessage] = useState(() => "Hello World!");

Providing a function to initialize state can be useful if we ever wanted to compute the initial value of a state property through some expensive computation.

Functional Updates

The function provided by useState() to update the state (in our case, setMessage()) is versatile. Besides accepting new values directly, it can also receive a function that gets the previous state as its argument. This capability is particularly useful when the new state depends on the previous one.

To illustrate this, let’s consider an enhancement to our HelloWorld component. Imagine we want to append an exclamation point (!) to the message every time a button is clicked. Instead of knowing the exact current state of our message, we would utilize the previous state to make the change.

import React, { useState } from "react";

const HelloWorld = () => {
  const [message, setMessage] = useState("Hello World!");

  return (
    <div>
      <h2>{message}</h2>
      <button onClick={() => setMessage((prevMessage) => prevMessage + "!")}>
        Add Exclamation
      </button>
    </div>
  );
};

In the above example, the setMessage() function uses the current message (retrieved as prevMessage) to append an exclamation point. This way, every time the button "Add Exclamation" is clicked, an exclamation point will be added to the end of the current message.

The button clicks now continue to add exclamation points to 'hello world!'

Try the above in the following CodeSandbox.

The functional update ensures that the state update relies on the most recent value of the state, making it both dynamic and dependent on the previous state.

When working with the useState() hook, it’s important to keep in mind the following points.

State Updates are Asynchronous

It’s always important to remember that state updates are asynchronous. As a result, React batches state updates for performance reasons. Therefore, always use the functional form of making state changes when the next state depends on the previous one.

Avoid Side Effects in useState

If initializing state with a function, the function used for this initialization should be pure. In other words, it should not have side effects or depend on mutable variables outside its scope.

Remember Re-renders

Always remember that changing state will cause the component to re-render! This point is important to be kept in mind to avoid unnecessary renders or infinite loops.

Wrap-up

The useState() hook is a cornerstone of state management in functional React components. Its versatility, from functional initialization to functional updates, makes it indispensable. As we’ve seen with other hooks, understanding its nuances can help us write clean, efficient and intuitive React code.


About the Author

Hassan Djirdeh

Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.

Related Posts

Comments

Comments are disabled in preview mode.