React Form Optimization: Populating dropdowns using APIs 🚀
Tags: webdev, react, frontend, tutorial
Published: 2023-12-11 05:31:31

_This blog was originally published on **medium**. Link to the article attached [here](https://medium.com/p/5ccd1ebe8322)._



Managing forms in React is a familiar task for developers. In this tutorial, I won’t be covering the basics of form creation. Instead, I’ll guide you through a specific scenario: handling multiple select fields (dropdowns) with values populated from APIs. Throughout, I’ll emphasize best practices to ensure your code is both clean and maintainable.

---

## Setting Up the Project

Before we start, make sure you have a React project initialized. You can use `create-react-app` or any other method of your choice.

Once your project is ready, create a new component for your form called `ErrorMessage.js`. This sample form is going to capture following fields to report an error.

1. **Name** — Name of the error — text field
2. **Error Code** — Error codes populated using API— Select field
3. **Service Team** — Team Names populated using API— Select field
4. **Description** — Description of the error — text field

As mentioned above, we have two Select fields called Error Code and Service Team. We need to populate these two Select fields using responses from APIs.

**Note:** This tutorial focuses exclusively on the UI aspect, and API creation is not within its scope. The explanation will be limited to the user interface implementation. Additionally, for simplicity, we won’t be incorporating advanced form-handling libraries such as React Hook Form or Formik. However, feel free to explore and integrate these libraries for more sophisticated form management.

This is how the code for error message form looks (_ErrorMessage1.jsx from repository_).

```react
import { useState, useEffect } from "react";
import axios from "axios";
import { API_URL } from "../constants";

const ErrorMessage = () => {
  const [formData, setFormData] = useState({
    name: "",
    serviceTeam: "",
    code: "",
    description: "",
  });

  const [codeOptions, setCodeOptions] = useState([]);
  const [serviceTeams, setServiceTeams] = useState([]);

  const fetchCodeOptions = async () => {
    try {
      const response = await axios.get(API_URL.CODES);
      setCodeOptions(response.data);
    } catch (error) {
      console.error("Error fetching code options:", error);
    }
  };

  const fetchServiceTeams = async () => {
    try {
      const response = await axios.get(API_URL.TEAMS);
      setServiceTeams(response.data);
    } catch (error) {
      console.error("Error fetching type teams:", error);
    }
  };

  useEffect(() => {
    fetchCodeOptions();
    fetchServiceTeams();
  }, []);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({ ...prevData, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // Add your form submission logic here
    console.log("Form submitted:", formData);
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label>
          Error Name:
          <input type="text" name="name" value={formData.name} onChange={handleChange} />
        </label>
        <label>
          Service Team:
          <select name="type" value={formData.serviceTeam} onChange={handleChange}>
            <option value="">Service Team</option>
            {serviceTeams.length > 0
              ? serviceTeams.map((team) => (
                  <option key={team.id} value={team.name}>
                    {team.name}
                  </option>
                ))
              : "loading teams.."}
          </select>
        </label>
        <label>
          Code:
          <select name="code" value={formData.code} onChange={handleChange}>
            <option value="">Error Code</option>
            {codeOptions.length > 0
              ? codeOptions.map((code) => (
                  <option key={code.id} value={code.value}>
                    {code.value}
                  </option>
                ))
              : "loading codes.."}
          </select>
        </label>
        <label>
          Description:
          <textarea name="description" value={formData.description} onChange={handleChange} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default ErrorMessage;
```

There is nothing wrong with this code. It will work as expected but there is some scope for making it better.

Since our main focus is on improving the way we call APIs, let’s concentrate on `useEffect` code block for now. This is how the `useEffect` is defined.

```react
useEffect(() => {
  fetchCodeOptions();
  fetchServiceTeams();
}, []);
```

To make sure, both the APIs are called and form is not visible until select fields are populated, we can do the following.

- Define a new state to track if API responses are received. Let’s call it formVisible.
- Also we can use Promise.all for calling APIs as shown below.

```react
const [formVisible, setFormVisible] = useState(false);

useEffect(() => {
  Promise.all([fetchCodeOptions(), fetchServiceTeams()])
    .then(() => setFormVisible(true))
    .catch((error) => console.error("Error fetching external APIs:", error));
}, []);
```

Now, I can use this state to toggle visibility of this form. JSX part of the code will be changed as shown below.

```react
const ErrorMessage = () => {
  // other states
  const [formVisible, setFormVisible] = useState(false);

  const fetchCodeOptions = async () => { /* logic goes here */ };
  const fetchServiceTeams = async () => { /* logic goes here */ };

  // rest of the functions

  useEffect(() => {
    Promise.all([fetchCodeOptions(), fetchServiceTeams()])
      .then(() => setFormVisible(true))
      .catch((error) => console.error("Error fetching external APIs:", error));
  }, []);

  return (
    <div>
      {formVisible ? (
        <form onSubmit={handleSubmit}>
          <label>
            Error Name:
            <input type="text" name="name" value={formData.name} onChange={handleChange} />
          </label>
          <label>
            Service Team:
            <select name="type" value={formData.serviceTeam} onChange={handleChange}>
              <option value="">Service Team</option>
              {serviceTeams.map((team) => (
                <option key={team.id} value={team.name}>
                  {team.name}
                </option>
              ))}
            </select>
          </label>
          <label>
            Code:
            <select name="code" value={formData.code} onChange={handleChange}>
              <option value="">Error Code</option>
              {codeOptions.map((code) => (
                <option key={code.id} value={code.value}>
                  {code.value}
                </option>
              ))}
            </select>
          </label>
          <label>
            Description:
            <textarea name="description" value={formData.description} onChange={handleChange} />
          </label>
          <button type="submit">Submit</button>
        </form>
      ) : (
        <p>Loading form...</p>
      )}
    </div>
  );
};
```

This will make sure that the form is not visible until all pre-required APIs are successful. (_refer ErrorMessage2.jsx from the repository_)

We can further improve API handling part as shown below. (_refer ErrorMessage3.jsx from the repository_)

```react
const fetchData = async () => {
  try {
    const [codeResponse, teamResponse] = await Promise.all([
      axios.get(API_URL.CODES), 
      axios.get(API_URL.TEAMS)
    ]);

    setCodeOptions(codeResponse.data);
    setServiceTeams(teamResponse.data);
    setFormVisible(true);
  } catch (error) {
    console.error("Error fetching data from APIs:", error);
  }
};

useEffect(() => {
  fetchData();
}, []);
```

Want to improve further? Let’s use an [Immediately Invoked Function Expression (IIFE)](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) to wrap the asynchronous logic. (_refer ErrorMessage4.jsx from the repository_)

```react
useEffect(() => {
  (async () => {
    try {
      const [codeResponse, teamResponse] = await Promise.all([
        axios.get(API_URL.CODES), 
        axios.get(API_URL.TEAMS)
      ]);

      setCodeOptions(codeResponse.data);
      setServiceTeams(teamResponse.data);
      setFormVisible(true);
    } catch (error) {
      console.error("Error fetching data from APIs:", error);
    }
  })();
}, []);
```

This way, you can keep the asynchronous logic separate without defining an additional function outside of the `useEffect` block.

## Handling Form Submissions

The `handleChange` function updates the form data as the user interacts with the form. The `handleSubmit` function is called when the form is submitted. In a real-world scenario, you would implement logic here to send the form data to a server or perform any necessary actions.

Other enhancements can include using loading spinners, having a common component for Select boxes etc.

[Link to the code repository](https://github.com/kpnigalye/react-select-fields-populated-with-apis)

---

## Conclusion

In the course of this tutorial, I’ve employed an incremental approach to refactoring the code. While there is room for additional enhancements within the form code, for the purposes of this tutorial, I have concentrated specifically on refining the Select fields. These fields are dynamically populated through APIs in React applications.

I hope that this explanation will be helpful to you! Let me know in comments.