How to Build A Dependent Select Box With ReactJS

How to Build A Dependent Select Box With ReactJS

Not too long ago, I built an HTML form with two select input fields and the options available in the second select input were to adapt and depend on the choice made in the first select input. So I decided to share how I built it.

It's called a dependent select Box, though it may also be referred to as a cascading dropdown or a dynamic dropdown. This feature is vital in web form development, as it enhances user experience and is also employed when precision and customization are required in form submissions.

Note: The full demo is at the end of this post.

Prerequisite:

For the tutorial, I assume you have

· Knowledge of HTML, CSS, and Modern Javascript

· Knowledge of React.js and React hooks


Getting Started

Set up a new React project or use your existing one.

For this tutorial, let’s set up a new React project using Vite with a React template. Follow these steps:

  1. Create a New Vite Project:

    Open your terminal and run the following command.

    Replace <project name> with your desired project name:

     npm create vite@latest <project name> -- --template react
    
  2. 2. Navigate to the project directory using your terminal:

     cd <project name>
    

    3. Install dependencies

     npm install
    

    3. Start Your React App:

     npm run dev
    

    This command will provide you with a URL where you can view and interact with your project.

Step 1: Understand the structure of your data:

To begin our journey into building the Dependent Select Box, it’s crucial to gain a deep understanding of the structure of the data that will power our dependent dropdowns as this will help you determine how you would design and implement your component.

In this tutorial, we’ll focus on creating our data for simplicity, but if you’re working with data you are not creating yourself, it’s equally important to study and adapt your component to match the data format.

In our case, the data structure will be an array of objects. Each object within this array represents a superOption, and it also contains an array of subOptions.

Let’s illustrate this with an example: Imagine you’re dealing with continents as the superOptions, and for each continent, there’s a list of countries nested within it as subOptions.

const data = [
    {
      superOption: "Africa",
      subOptions: ["Nigeria", "Kenya", "South Africa"],
    },
    {
      superOption: "Asia",
      subOptions: ["India", "China", "Japan"],
    },
    // ... add more continents and countries
  ];

This hierarchical structure allows us to logically organize our data, mirroring real-world scenarios where one selection depends on another.

Step 2: Create the select input UI:

With a clear understanding of our data structure, it’s time to build the user interface (UI) that interacts with this data.

Let’s create a React functional component in the src folder that features two controlled select inputs. Additionally, we’ll implement a state variable called countries to store a list of countries associated with the selected continent.

We’ve conveniently placed our data array at the top of the component file for clarity and easy reference.

SelectForm.tsx

// Import useState from React.
import { useState} from "react";

 //Define the data structure
const data = [
    {
      superOption: "Africa",
      subOptions: ["Nigeria", "Kenya", "South Africa"],
    },
    {
      superOption: "Asia",
      subOptions: ["India", "China", "Japan"],
    },
    // ... add more continents and countries
  ];

function SelectForm() {
  // Set up your states for continent and country selection
  const [selectedContinent, setSelectedContinent] = useState("");
  const [selectedCountry, setSelectedCountry] = useState("");

  // Create a state to store the countries
  const [countries, setCountries] = useState([]);


  // Create the form with select inputs
  return (
    <form>
      <div>
        <label htmlFor="continentSelect">Select Continent:</label>
        <select
          id="continentSelect"
          name="continent"
          value={selectedContinent}
          onChange={(e) => setSelectedContinent(e.target.value)}
        >
          <option value="">Select Continent</option>
          {data.map((continent) => (
            <option key={continent.superOption} value={continent.superOption}>
              {continent.superOption}
            </option>
          ))}
        </select>
      </div>

      <div>
        <label htmlFor="countrySelect">Select Country:</label>
        <select
          id="countrySelect"
          name="country"
          value={selectedCountry}
          onChange={(e) => setSelectedCountry(e.target.value)}
          disabled={countries.length === 0}
        >
          <option value="">Select Country</option>
          {countries.map((country) => (
            <option key={country} value={country}>
              {country}
            </option>
          ))}
        </select>
      </div>
    </form>
  );
}

export default SelectForm;

Here’s an explanation of each step:

  1. We import useState from React.

  2. We set up state variables selectedContinent and selectedCountry to control the selected values for continent and country. They are initialized with empty strings

  3. We create a state variable countries to store the list of countries for the selected continent. It’s initialized as an empty array.

  4. Inside the return statement, we create the form with two select inputs for continent and country. We control the select inputs by setting the value and onChange attributes to manage the selected values.

  5. For the first select input (continent), we loop through the data array using map() to generate the options dynamically.

  6. For the second select input (country), we also use map() to generate the options based on the countries array.

  7. We set the disabled attribute to prevent selection when there are no countries available in the countries array, this is to prevent users from trying to select a country when no continent has been selected.

Step 3: Add a function to handle retrieving the data:

We have to retrieve the object corresponding to the selected continent and then get the subOptions of that object. To do this, we will need to iterate through the data array and find the object that matches the selected superOption value (the continent), There are several approaches to achieve this, but in this tutorial, we’ll use the find() array method.

The find () method iterates through an array and returns the first element in the provided array that satisfies the provided testing function. This is appropriate because our data does not have any repeated superOption values.

const retrieveCountries = () => {
    //Check if No Continent is Selected
    if (selectedContinent === "") {
      setCountries([]); // Reset countries when no continent is selected
    } else {
      // Find the Selected Continent Object
      const selectedContinentObject = data.find(
        (continentObj) => continentObj.superOption === selectedContinent //testing function
      );
      //Update Countries Based on Selected Continent
      if (selectedContinentObject) {
        const countriesOfSelectedContinent = selectedContinentObject.subOptions;
        setCountries(countriesOfSelectedContinent);
        //Reset Selected Country when the continent changes
        setSelectedCountry("");
      }
    }
  };

We’ve added the retrieveCountries function, which is responsible for fetching the countries based on the selected continent.

Here’s an explanation of each step:

  1. we begin by checking if any continent is selected (selectedContinent === ""). If no continent is selected, it resets the countries state to an empty array, effectively clearing the country dropdown.

  2. If a continent is selected, we Find the selected continent Object in the data array. It uses the find method, which iterates through the data array and looks for the first object that has a superOption value matching the selected continent value, then the result is stored in the selectedContinentObject variable.

  3. If a matching continent object is found, it extracts the subOptions(countries) of that continent object and assigns them to the countriesOfSelectedContinent variable. It then updates the countries state with these subOptions, effectively populating the country dropdown with the countries of the selected continent.

  4. Finally, it resets the selectedCountry state to an empty string. This step ensures that if a user has previously selected a country from another continent and then changes the continent, the selected country is cleared to prevent any mismatch.

Step 4: Call the retrieveCountries function when a continent is selected

Finally, we add an onClick event handler to the continent select input to trigger the retrieveCountries function when the select box is clicked.

This ensures that the dependent select box behaviour is activated when the user interacts with the continent dropdown.

<div>
          <label htmlFor="continentSelect">Select Continent:</label>
          <select
            id="continentSelect"
            name="continent"
            value={selectedContinent}
            onChange={(e) => {
              setSelectedContinent(e.target.value);
            }}
            onClick={retrieveCountries}  // Call the function when select is clicked 
          >
            <option value="">Select Continent</option>
            {data.map((continent) => (
              <option key={continent.superOption} value={continent.superOption}>
                {continent.superOption}
              </option>
            ))}
          </select>
  </div>

Now, the countries state will be dynamically updated based on the selected continent, creating a cascading or dependent select box behaviour.

Full demo

import { useState } from "react";

  const data = [
    {
      superOption: "Africa",
      subOptions: ["Nigeria", "Kenya", "South Africa"],
    },
    {
      superOption: "Asia",
      subOptions: ["India", "China", "Japan"],
    },
    // ... add more continents and countries
  ];

function SelectForm() {
  const [selectedContinent, setSelectedContinent] = useState("");
  const [selectedCountry, setSelectedCountry] = useState("");

  const [countries, setCountries] = useState([]);

  const retrieveCountries = () => {
    if (selectedContinent === "") {
      setCountries([]);
    } else {
      const selectedContinentObject = data.find(
        (continentObj) => continentObj.superOption === selectedContinent //testing function
      );

      if (selectedContinentObject) {
        const countriesOfSelectedContinent = selectedContinentObject.subOptions;
        setCountries(countriesOfSelectedContinent);
        setSelectedCountry("");
      }
    }
  };

  return (
    <div>
      <h1>Select a Continent and a Country</h1>
      <form>
        <div>
          <label htmlFor="continentSelect">Select Continent:</label>
          <select
            id="continentSelect"
            name="continent"
            value={selectedContinent}
            onChange={(e) => {
              setSelectedContinent(e.target.value);
            }}
            onClick={retrieveCountries}
          >
            <option value="">Select Continent</option>
            {data.map((continent) => (
              <option key={continent.superOption} value={continent.superOption}>
                {continent.superOption}
              </option>
            ))}
          </select>
        </div>
        <div>
          <label htmlFor="countrySelect">Select Country:</label>
          <select
            id="countrySelect"
            name="country"
            value={selectedCountry}
            onChange={(e) => setSelectedCountry(e.target.value)}
            disabled={countries.length === 0}
          >
            <option value="">Select Country</option>
            {countries.map((country) => (
              <option key={country} value={country}>
                {country}
              </option>
            ))}
          </select>
        </div>
      </form>
    </div>
  );
}

export default SelectForm;