Dropdown data binding with React hooks
Tags: react
Published: 2020-01-29 04:16:39

---
title: Dropdown data binding with React hooks
published: true
description: How to populate a dropdown with data from a web API using React hooks
tags: react
cover_image: https://thepracticaldev.s3.amazonaws.com/i/al17c8clpp6ijy6f6pmv.png
canonical_url: https://www.carlrippon.com/drop-down-data-binding-with-react-hooks/
---

Dropdown data binding is always interesting in different UI technologies. We often want to feed the dropdown a list of dynamic data values from a web API. Usually, we want to stop the user from interacting with the dropdown while the items are being loaded. We may wish to select a particular dropdown item after they have loaded as well. So, how do we do all this with [React hooks](https://reactjs.org/docs/hooks-intro.html)? Let's find out.

## Creating the dropdown component

Our dropdown is going to consist of character names from Star Wars. Let's make a start on the React component.

```jsx
function CharacterDropDown() {
  return (
    <select>
      <option value="Luke Skywalker">
        Luke Skywalker
      </option>
      <option value="C-3PO">C-3PO</option>
      <option value="R2-D2">R2-D2</option>
    </select>
  );
}
```

This is a functional React component containing 3 hardcoded characters. Although the item labels are the same as the item values in our example, we've explicitly specified them both because often they are different in other scenarios.

A nice and simple start but there is still a lot of work to do!

## Using state to render dropdown items

Our dropdown contains hardcoded items at the moment. What if items need to be dynamic and loaded from an external source like a web API? Well, the first thing we need to do to make the item's dynamic is to put the items in state. We can then have the dropdown reference this state when rendering its items:

```jsx
function CharacterDropDown() {
  const [items] = React.useState([
    {
      label: "Luke Skywalker",
      value: "Luke Skywalker"
    },
    { label: "C-3PO", value: "C-3PO" },
    { label: "R2-D2", value: "R2-D2" }
  ]);
  return (
    <select>
      {items.map(item => (
        <option
          key={item.value}
          value={item.value}
        >
          {item.label}
        </option>
      ))}
    </select>
  );
}
```

We use the `useState` hook to create some state with our characters in. The parameter for `useState` is the initial value of the state. The `useState` hook returns the current value of the state in the first element of an array - we've destructured this into an `items` variable.

So, we have an `items` variable which is an array containing our Star Wars characters. In the `return` statement, we use the `items` array `map` function to iterate through the characters and render the relevant `option` element. Notice that we set the `key` attribute on the `option` element to help React make any future changes to these elements.

We can arguably make the JSX a little cleaner by destructuring the `label` and `value` properties from the item that is being mapped over and then referencing them directly:

```jsx
<select>
  {items.map(({ label, value }) => (
    <option key={value} value={value}>
      {label}
    </option>
  ))}
</select>
```

## Fetching data from a web API

We are going to populate a dropdown with characters from the fantastic [Star Wars API](https://swapi.co/). So, instead of putting 3 hardcoded characters in the state, we need to put data from `https://swapi.co/api/people` into it. We can do this with the `useEffect` hook:

```jsx
function CharacterDropDown() {
  const [items, setItems] = React.useState([]);

  React.useEffect(() => {
    async function getCharacters() {
      const response = await fetch("https://swapi.co/api/people");
      const body = await response.json();
      setItems(body.results.map(({ name }) => ({ label: name, value: name })));
    }
    getCharacters();
  }, []);

  return (
    ...
  );
}
```

Let's examine the `useEffect` hook:

- Its first parameter is a function to execute when the _side effect_ runs
- The second parameter determines _when_ the side effect runs. In our case this is just after the component first renders because we have specified an empty array
- Our side effect function in the `useEffect` hook needs to be asynchronous because of the web API call, but this isn't directly allowed in the `useEffect`. This is why we have an asynchronous nested `getCharacters` function that is called
- Inside the `getCharacters` function we use the native [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) function to make the web API request. We then map the response body to the data structure that our `items` state expects

Let's turn our attention to the `useState` hook again:

- Notice that we now default the `items` state to an empty array
- Notice also that we have destructured the 2nd parameter from the `useState` hook. This is a function called `setItems`, which we can use to set a new value for the `items` state.
- We use the `setItems` function to set the `items` state in the `getCharacters` function after we have mapped the data appropriately from the web API. This call to `setItems` will cause our component to re-render and show the dropdown items.

## Stopping the user interact with the dropdown while items are loading

We probably want to stop the user from interacting with the dropdown while the data is being loaded. We can do this by disabling the dropdown whist the web API request is being made:

```jsx
function CharacterDropDown() {
  const [loading, setLoading] = React.useState(true);
  const [items, setItems] = React.useState([
    { label: "Loading ...", value: "" }
  ]);
  React.useEffect(() => {
    async function getCharacters() {
      ...
      setItems(body.results.map(({ name }) => ({ label: name, value: name })));
      setLoading(false);
    }
    getCharacters();
  }, []);
  return (
    <select disabled={loading}>
      ...
    </select>
  );
}
```

We've added a new piece of state called `loading` to indicate whether items are being loaded. We initialise this to `true` and set it to `false` after the items have been fetched from the web API and set in the `items` state.

We then reference the `loading` state on the `select` elements `disabled` property in the JSX. This will disable the `select` element while its items are being loaded.

Notice that we've defaulted the `items` state to an array with a single item containing a "Loading .." label. This is a nice touch that makes it clear to the user what is happening.

## Aborting loading items when the component is unmounted

What happens if the user navigates to a different page, and `CharacterDropDown` is unmounted while the items are still being fetched? React won't be happy when the response is returned, and state is attempted to be set with the `setItems` and `setLoading` functions. This is because this state no longer exists. We can resolve this by using an `unmounted` flag:

```jsx
React.useEffect(() => {
  let unmounted = false;
  async function getCharacters() {
    const response = await fetch(
      "https://swapi.co/api/people"
    );
    const body = await response.json();
    if (!unmounted) {
      setItems(
        body.results.map(({ name }) => ({
          label: name,
          value: name
        }))
      );
      setLoading(false);
    }
  }
  getCharacters();
  return () => {
    unmounted = true;
  };
}, []);
```

So, we initialise `unmounted` to `false` and check that it is still false before the state is set.

The side effect function in the `useEffect` hook can return another function that is executed when the component is unmounted. So, we return a function that sets our `unmounted` to `true`.

Our dropdown is nice and robust now.

## Controlling the dropdown value with state

A common pattern when building a form is to control the field values in state, so, let's now control the dropdown value with state:

```jsx 
function CharacterDropDown() {
  const [loading, setLoading] = React.useState(true);
  const [items, setItems] = React.useState(...);
  const [value, setValue] = React.useState();
  React.useEffect(...);
  return (
    <select
      disabled={loading}
      value={value}
      onChange={e => setValue(e.currentTarget.value)}
    >
      ...
    </select>
  );
}
```

We've added a new piece of state called `value` and bound that to the `value` prop on the `select` element in the JSX. We also update this state in a `change` event listener with the `onChange` prop.

## Setting the initial value

We may want to select an initial value of the dropdown. Now that the value is controlled by state, this is a simple matter of setting the default value of the state:

```jsx
const [value, setValue] = React.useState(
  "R2-D2"
);
```

## Wrap up

- We use the `useEffect` hook to load drop down items from a web API. The side effect function needs to contain a nested function that does the web API call
- We use the `useState` hook for a loading flag that is set while drop down items are loading which can be used to disable the drop down during this process
- We use the `useState` hook to hold drop down items in state. This is set after the data has been fetched from the web API
- We also use the `useState` hook to control the selected drop down value in state. We can then set the initial selected value for the drop down by setting the initial value for the state


*Originally published at [https://www.carlrippon.com/drop-down-data-binding-with-react-hooks](https://www.carlrippon.com/drop-down-data-binding-with-react-hooks/) on Jan 28, 2020*.