DEV Community

Cover image for React Form Optimization: Populating dropdowns using APIs ๐Ÿš€
Krishna Nigalye
Krishna Nigalye

Posted on • Updated on

React Form Optimization: Populating dropdowns using APIs ๐Ÿš€

This blog was originally published on medium. Link to the article attached here.

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).

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;
Enter fullscreen mode Exit fullscreen mode

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.

useEffect(() => {
  fetchCodeOptions();
  fetchServiceTeams();
}, []);
Enter fullscreen mode Exit fullscreen mode

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.
const [formVisible, setFormVisible] = useState(false);

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

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

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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)

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();
}, []);
Enter fullscreen mode Exit fullscreen mode

Want to improve further? Letโ€™s use an Immediately Invoked Function Expression (IIFE) to wrap the asynchronous logic. (refer ErrorMessage4.jsx from the repository)

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);
    }
  })();
}, []);
Enter fullscreen mode Exit fullscreen mode

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


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.

Top comments (0)