Managing component state with the useState Hook

This article covers

If you’re building React apps, then you’re expecting the data your app uses to change over time. Whether it’s fully server-rendered, a mobile app or all in a browser, your application’s user interface should represent the current data or state at the time of rendering. Sometimes multiple components throughout the app will use the data, and sometimes a component doesn’t need to share its secrets and can manage its own state without the help of mammoth, application-wide state-store behemoths. In this article, we’ll keep it personal and concentrate on components taking care of themselves, without regard for other components around them.

Figure 1 shows a very basic illustration of React’s job: it should use the current state to render the UI. If the state changes, React should re-render the UI. We usually want the state and UI to always be in sync (although we might choose to delay synchronization during state transitions, like when fetching data).

Changing the state should update the UI

React provides a couple of functions, or hooks, to enable it to track values in your components and keep the state and UI in sync. For single values it gives us the useState hook and that’s the hook we’ll explore in this article.

There’s a little bit of housekeeping to set up our example app but once that’s done we’ll be able to concentrate on a single component.

1 A bookings manager: setting up the App

Your fun but professional company has a number of resources that can be booked by staff: meeting rooms, AV equipment, technician time, table football and even party supplies. One day, the boss asks you to create a component for the company network that lets staff select a resource to book in a booking application. The component should display a list of resources, or bookables, filtered by group, and highlight the one currently selected. When a user selects a bookable, its details can be displayed, as shown in Figure 2.

The list of bookables

The container App that uses our components will change very little. We’ll introduce its key parts in the next two sections.

1.1 Bookings app project structure

Use create-react-app to generate a new React project called bookings. From the public folder, remove all but index.html. From the src folder, remove all but index.js and App.css. The file listing on the left of Figure 3 highlights the files to remove.

Project setup

The file listing on the right of Figure 3 highlights the six key files that will always be part of our bookings application. There are three that will not change:

The version of the Bookables component we’ll work on in this article will be in the Bookables folder. Within each version folder there will be at least these two component files:

The last file is the entry point into the application:

First, let’s create the first five files that won’t change.

1.2 Five files that won’t change

We’ll start work on the Bookables component itself in Section 2. In this section we’ll set up the remaining five key files.

Inside the public folder, edit the index.html file. A lot of the create-react-app generated boilerplate can come out. The div element with an id of root must remain and will be the container element for the app. React will render the App component into that div. You can also set the title for the page, as has been done in Listing 1. (You can find the code examples for React Hooks in Action on GitHub at https://github.com/jrlarsen/ReactHooks organized by chapter and by focus.)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Bookings App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>

In Listing 2 you can see that App.js currently just renders the Bookables component in an enclosing div, passing the component the bookables data.

import React from "react"; import "../App.css"; import bookablesData from "../bookablesData"; import Bookables from "./Bookables"; const App = () => { return ( <div className="App"> <Bookables bookablesByGroup={bookablesData} /> </div> ); }; export default App;

App.js pulls in the Bookables component from the same folder. App.js also imports the data for the app, the list of bookable resources for the company organized by group, as shown in Listing 3.

const bookablesData = { rooms: [ { title: "Lounge", notes: "A relaxing place to hang out." }, { title: "Meeting Room", notes: "Formal. Seats 12." }, { title: "Games Room", notes: "Table tennis, table football, pinball!" } ], kit: [ { title: "Projector", notes: "Powerful and sharp." }, { title: "Wireless mics", notes: "Really handy but don't forget to switch them off." } ] }; export default bookablesData;

Feel free to add more groups and bookables and more detailed notes for each item. There are also just a few styles to prettify the examples in the article, as shown in Listing 4.

.App { font-family: sans-serif; } main { text-align: center; margin: 40px; display: grid; grid-template-columns: 1fr 5fr; grid-column-gap: 20px; } .bookables { margin: 1em auto; padding: 0; list-style: none; } .bookables > li { margin: 0 0 1em; padding: 1em; border: 1px solid silver; } .selected { background: #ddd; }

Before we get started on the Bookables component itself, we still need a way in to the application, a file that imports the App component for the current version. Let’s get that vital piece of plumbing sorted. In the root folder of the application, edit the index.js file to look like Listing 5. It imports the App component and renders the App into the root div seen in Listing 1.

import React from 'react'; import ReactDOM from 'react-dom'; import App from './Bookables/App'; ReactDOM.render( <App />, document.getElementById('root') );

Okay, with our app plumbing securely in place, let’s turn on the faucet and get some data flowing into our first Bookables component!

2 Storing, using and setting values with useState

Your React components provide UI for your application. Sometimes they are unchanging - maybe headers or logos or footers - but often they include data that changes. If components are just functions, how can they persist their state across renders? Are their variables not lost when they finish executing? Well, the simplest approach to manage state is to engage React’s help, with the useState hook.

2.1 Calling useState returns a value and an updater function

We want to alert React that a value used within a component has changed so it can re-run the component and update the UI if necessary. We need a way of changing that value, some kind of updater function, that triggers React to call the component with the new value and get the updated UI, as shown in Figure 4.

Use an updater function to change the state

In fact, to avoid our component state value disappearing when the component code finishes running, we can get React to manage the value for us. That’s what the useState hook is for. Every time React calls our component to get hold of its UI, the component can ask React for the latest value and for the value’s updater function. The component can use the value when generating its UI and use the updater function when changing the value, for example in response to events.

Calling useState returns a value and its updater function as a tuple, an array with two elements, as shown in Figure 5.

useState syntax

You could assign the returned array to a variable, and then access the two elements individually, by index, like this:

const selectedRoomArray = useState(); const selectedRoom = selectedRoomArray[0]; const setSelectedRoom = selectedRoomArray[1];

But, it’s more common to use array destructuring and assign the returned elements to variables in one step:

const [ selectedRoom, setSelectedRoom ] = useState();

Array destructuring lets us assign elements in an array to variables of our choosing. The names selectedRoom and setSelectedRoom are arbitrary and our choice, although it’s common to start the variable name for the second element, the updater function, with set. The following would work just as well:

const [ myRoom, updateMyRoom ] = useState();

If you want to set an initial value for the variable, pass it as an argument to the useState function. When React first runs your Component, useState will return the two-element array as usual but React will assign the initial value to the first element of the array, as shown in Figure 6.

useState syntax with initial value

The first time the following line of code is executed within a component, the selected variable will be assigned the value “Lecture Hall”:

const [ selected, setSelected ] = useState("Lecture Hall");

Let’s create the Bookables component to use the useState hook to ask React to manage the value of the selected item’s index. We’ll pass it 1 as the initial index. You should see the Lecture Hall highlighted when the Bookables component first appears on the screen, as shown in Figure 7.

Screenshot of Bookables list

Listing 6 shows the code for the component. It includes an event handler that uses the updater function assigned to setBookableIndex to change the selected index when a user clicks a bookable.

import React, { useState } from 'react'; function Bookables ({ bookablesByGroup }) { const group = "rooms"; const bookables = bookablesByGroup[group] || []; const [ bookableIndex, setBookableIndex ] = useState(0); function changeBookable (selectedIndex) { setBookableIndex(selectedIndex); } return ( <ul className="bookables"> {bookables.map((b, i) => ( <li key={b.title} className={i === bookableIndex ? "selected" : null} onClick={() => changeBookable(i)} > {b.title} </li> ))} </ul> ); } export default Bookables;

React runs the Bookables component code, providing the value used by the component to generate the UI. When a user clicks on a bookable, the handleClick event handler uses the updater function to tell React to update the chosen value. React then runs the Bookables code again, this time passing the updated value, letting the component generate the updated UI. React can then use the newly generated UI to update the display.

At this point, our Bookables component is very simple. But there are already some fundamental concepts at work, concepts that underpin our understanding of functional components and React Hooks. Having a strong grasp of these concepts will make future discussions and your expert use of hooks much easier. In particular, here are five key concepts:

In order to discuss concepts with clarity and precision, from time to time we’ll take stock of the key words and objects we’ve encountered so far. Table 1 lists and describes some of the terms we’ve come across:

Icon Term Description
Component icon Component A function that accepts props and returns a description of its UI.
Initial value icon Initial value The component passes this value to useState. React sets the state value to this initial value when the component first runs.
Updater function icon Updater function The component will call this function to update the state value.
Event handler icon Event handler A function that runs in response to an event of some kind. For example, a user clicking a bookable.
UI icon UI A description of the elements that make up a user interface. The state value is often included somewhere in the UI.

Figure 8 shows some of the steps involved when our Bookables component runs and a user clicks a bookable. Table 2 discusses each step.

Step cycle diagram for useState

Step What happens? Discussion
1 React calls the component. To generate the UI for the page, React traverses the tree of components, calling each one. React will pass each component any props set as attributes in the JSX.
2 The component calls useState for the first time. The component passes the initial value to the useState function. React sets the current value for that useState call from the component.
3 React returns the current value and an updater function as an array. The component code assigns the value and updater function to variables for later use. The second variable name often starts with set. E.g. value and setValue.
4 The component sets up an event handler. The event handler may listen for user clicks, timers firing or resources loading, for example. The handler will call the updater function to change the state value.
5 The component returns its UI. The component uses the current state value to generate its user interface and returns it, finishing its work.
6 The event handler calls the updater function. An event fires and the handler runs. The handler uses the updater function to change the state value.
7 React updates the state value. React replaces the state value with the value passed by the updater function.
8 React calls the component. React knows the state value has changed so must recalculate the UI.
9 The component calls useState for the second time. This time, React will ignore the argument.
10 React returns the current state value and the updater function. React has updated the state value. The component needs the latest value.
11 The component sets up an event handler. This is a new version of the handler and may use the newly updated state value.
12 The component returns its UI. The component uses the current state value to generate its user interface and returns it, finishing its work.

React Hooks in Action

Keep learning! React Hooks in Action with Suspense and Concurrent Mode is my new book, published by Manning and available as an early access title (MEAP).

Find out more.

React Hooks in Action