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:
- The state property we want to create.
- 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.”
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.
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.
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.