4 Examples of the useState Hook

By Dave Ceddia updated

The useState Hook

There are a bunch of React hooks, but useState is the workhorse of the bunch.

In this article, we’ll cover useState with simple types, as well as useState with objects and arrays. We’ll also see a couple ways of storing multiple values.

Up until React 16.8, if you started writing a function component and then ran into a situation where you needed to add state, you’d have to convert the component to a class.

Today, you can get that same functionality with the useState hook, and save yourself the work!

FYI if you’re brand new to React, I suggest pausing here and reading my React tutorial first – it covers all the important basics – and then come back here for the more advanced uses of useState.

What Does the React.useState Hook Do?

The useState hook lets you add state to function components.

By calling React.useState inside a function component, you create a single piece of state associated with that component. (every hook starts with the word “use”; a call to useState literally lets you “use state” in a function component)

In classes, the state is always an object, and you can store multiple values in that object.

But with hooks, the state can be any type you want – you can useState with an array, useState an object, a number, a boolean, a string, whatever you need. Each call to useState creates a single piece of state, holding a single value of any type.

The useState hook is perfect for local component state, and a small-ish amount of it. For a larger app, or one that you intend to scale up, you’ll probably want to augment useState with some other state management solutions.

Example: Show/Hide a Component (useState with a boolean)

This example is a component that displays some text with a “read more” link at the end, and will expand to show the rest of the text when the link is clicked.

Or if you’re more the video type, watch me build a similar component here:

useState Hook Examples

Read through the comments to see what’s happening here:

// First: import useState. It's a named export from 'react'
// Or we could skip this step, and write React.useState
import React, { useState } from 'react';
import ReactDOM from 'react-dom';

// This component expects 2 props:
//   text - the text to display
//   maxLength - how many characters to show before "read more"
function LessText({ text, maxLength }) {
  // Create a piece of state, and initialize it to `true`
  // `hidden` will hold the current value of the state,
  // and `setHidden` will let us change it
  const [hidden, setHidden] = useState(true);

  // If the text is short enough, just render it
  if (text.length <= maxLength) {
    return <span>{text}</span>;
  }

  // Render the text (shortened or full-length) followed by
  // a link to expand/collapse it.
  // When a link is clicked, update the value of `hidden`,
  // which will trigger a re-render
  return (
    <span>
      {hidden ? `${text.substr(0, maxLength).trim()} ...` : text}
      {hidden ? (
        <a onClick={() => setHidden(false)}> read more</a>
      ) : (
        <a onClick={() => setHidden(true)}> read less</a>
      )}
    </span>
  );
}

ReactDOM.render(
  <LessText
    text={`Focused, hard work is the real key
      to success. Keep your eyes on the goal, 
      and just keep taking the next step 
      towards completing it.`}
    maxLength={35}
  />,
  document.querySelector('#root')
);

Try out the working example in this CodeSandbox!

With just this one line of code, we’ve made this function stateful:

const [hidden, setHidden] = useState(true);

Once that’s done, the “read more” / “read less” links just need to call setHidden when they’re clicked.

useState returns an array with 2 elements, and we’re using ES6 destructuring to assign names to them. The first element is the current value of the state, and the second element is a state setter function – just call it with a new value, and the state will be set and the component will re-render.

const [hidden, setHidden] = useState(true);

But what is this function doing, really? If it gets called every render (and it does!), how can it retain state?

Tricksy Hooks

The “magic” here is that React maintains an object behind the scenes for each component, and in this persistent object, there’s an array of “state cells.” When you call useState, React stores that state in the next available cell, and increments the pointer (the array index).

Assuming that your hooks are always called in the same order (which they will be, if you’re following the Rules of Hooks), React is able to look up the previous value for that particular useState call. The first call to useState is stored in the first array element, the second call in the second element, and so on.

It’s not magic, but it relies on a truth you may not have thought about: React itself is calling your component, so it can set things up beforehand.

// Pseudocode to illustrate what React is doing

// Keep track of which component is being called
// And keep a list of hooks
currentComponent = YourComponent;
hooks[currentComponent] = []

// Call the component. If it calls useState,
// that'll update the `hooks` above
YourComponent();

Example: Updating state based on previous state (useState with a number)

Let’s look at another example: updating the value of state based on the previous value.

We’ll build a, uh, “step tracker.” Very easy to use. Just like a Fitbit. Every time you take a step, simply click the button. At the end of the day, it will tell you how many steps you took. I’m working on securing my first round of funding as you read this.

import React, { useState } from 'react';

function StepTracker() {
  const [steps, setSteps] = useState(0);

  function increment() {
    setSteps(prevState => prevState + 1);
  }

  return (
    <div>
      Today you've taken {steps} steps!
      <br />
      <button onClick={increment}>
        I took another step
      </button>
    </div>
  );
}

ReactDOM.render(
  <StepTracker />,
  document.querySelector('#root')
);

First, we’re creating a new piece of state by calling useState, initializing it to 0. It returns an array containing that initial value, along with a function for updating it. We’re destructuring that into variables called steps and setSteps. We also wrote an increment function to increase the step counter.

You’ll notice we’re using the functional or “updater” form of setSteps here. We’re passing a function instead of a value.

React will call that updater function with the previous value of the state, and whatever you return will replace the state with a new value. The argument is called prevState in the example but you can name it anything.

We could just call setSteps(steps + 1) and it would work the same in this example… but I wanted to show you the updater form, because it’ll be useful in case your update is happening in a closure which has captured a stale value of the state.

Another thing we’ve done here is write a standalone increment function, instead of inlining the arrow function on the button’s onClick prop. We could have written button this way and it would’ve worked just the same:

<button onClick={() => setSteps(prevState => prevState + 1)}>
  I took another step
</button>

Either way is fine. Sometimes the inline functions look messy and I’ll pull them out, and sometimes they’re succinct and I leave them inline.

Example: useState with an array

Remember: state can hold any kind of value! Here’s an example of useState holding an array.

Typing into the box and hitting Enter will add an item to the list.

function ListOfThings() {
  const [items, setItems] = useState([]);
  const [itemName, setItemName] = useState("");

  const addItem = event => {
    event.preventDefault();
    setItems([
      ...items,
      {
        id: items.length,
        name: itemName
      }
    ]);
    setItemName("");
  };

  return (
    <>
      <form onSubmit={addItem}>
        <label>
          <input
            name="item"
            type="text"
            value={itemName}
            onChange={e => setItemName(e.target.value)}
          />
        </label>
      </form>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

Notice we’re calling useState with an initial value of an empty array [], and take a look at the addItem function.

The state updater (setItems, here) doesn’t “merge” new values with old – it overwrites the state with the new value. This is very different from how this.setState worked in classes!

So in order to add an item to the array, we’re using the ES6 spread operator ... to copy the existing items into the new array, and inserting the new item at the end.

Also, just to note, this example uses const and an arrow function instead of the function from the last example. I wanted to show that either way works.

Example: multiple calls to useState

If you want to store multiple values in a function component, you’ve got a couple options:

  • call useState more than once
  • shove everything into an object

There’s nothing wrong with calling useState multiple times, and in most cases, that’s how I store multiple values. Once you get over 4 or 5 useState calls it gets a bit unwieldy, but if you’re fine with it, React is too.

Let’s look at how you’d call useState a couple times to store a username and password.

function LoginForm() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const printValues = e => {
    e.preventDefault();
    console.log(username, password);
  };

  return (
    <form onSubmit={printValues}>
      <label>
        Username:
        <input
          value={username}
          onChange={event => setUsername(event.target.value)}
          name="username"
          type="text"
        />
      </label>
      <br />
      <label>
        Password:
        <input
          value={password}
          onChange={event => setPassword(event.target.value)}
          name="password"
          type="password"
        />
      </label>
      <br />
      <button>Submit</button>
    </form>
  );
}

You can see it works pretty much the same as calling useState once. Each call creates a new piece of state and a setter for that state, and we store those in variables.

When the user types into the inputs, the onChange handler is called. It receives the input event and updates the appropriate piece of state with the new value for the username/password.

Example: useState with an object (multiple values, sorta)

Let’s look at an example where state is an object. We’ll create the same login form with 2 fields. Compare both ways and pick your favorite.

To store multiple values in useState, you have to put them into a single object, and be careful about how you update the state.

function LoginForm() {
  const [form, setState] = useState({
    username: '',
    password: ''
  });

  const printValues = e => {
    e.preventDefault();
    console.log(form.username, form.password);
  };

  const updateField = e => {
    setState({
      ...form,
      [e.target.name]: e.target.value
    });
  };

  return (
    <form onSubmit={printValues}>
      <label>
        Username:
        <input
          value={form.username}
          name="username"
          onChange={updateField}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          value={form.password}
          name="password"
          type="password"
          onChange={updateField}
        />
      </label>
      <br />
      <button>Submit</button>
    </form>
  );
}

Try it out in this CodeSandbox.

First up, we’re creating a piece of state and initializing it with an object:

const [form, setState] = useState({
  username: '',
  password: ''
});

This looks a lot like how you might initialize state in a class.

Then we have a function to handle the submission, which does a preventDefault to avoid a page refresh and prints out the form values.

const printValues = e => {
  e.preventDefault();
  console.log(form.username, form.password);
};

(We called the updater setState, but you can call it whatever you like)

The updateField function is where the real work happens. It calls setState and passes an object, but it must be sure to copy in the existing state with ...form if it doesn’t want to overwrite it. Try taking out the ...form line and see what happens.

At the bottom we have a pretty standard-looking chunk of JSX to render the form and its inputs. Since we’ve passed a name prop to the inputs, the updateField function can use it to update the appropriate state. This way you can avoid having to write a handler function for each field.

An Easier Way with useReducer?

If you have complex state, then storing multiple values in useState can get cumbersome.

The next level up is the useReducer hook which is better suited to managing state with multiple values. Learn about the useReducer hook here.

Beyond useReducer, there’s the Context API and a bunch of third-party state management libraries. Check out my Advanced State Management Guide for an overview of the ecosystem.

Learning React can be a struggle — so many libraries and tools!
My advice? Ignore all of them :)
For a step-by-step approach, check out my Pure React workshop.

Pure React plant

Learn to think in React

  • 90+ screencast lessons
  • Full transcripts and closed captions
  • All the code from the lessons
  • Developer interviews
Start learning Pure React now

Dave Ceddia’s Pure React is a work of enormous clarity and depth. Hats off. I'm a React trainer in London and would thoroughly recommend this to all front end devs wanting to upskill or consolidate.

Alan Lavender
Alan Lavender
@lavenderlens