Search code examples
javascriptreactjsdropdown

Dependent Dropdown with React Hooks


I am new to react and have been having hard time implementing this dependent dropdown. I want to get the lists of states and filter out location that renders to the second dropdown. Below is what i tried implementing. The States and Location data is pulled from an API.

I was able to pull the states data but got stuck at implementing the dependent dropdown. Any help will be appreciated.

import React, { useState, useEffect } from 'react'

function NullView() {

    // //API CALL
    const [error, setError] = useState(null);
    const [isLoaded, setIsLoaded] = useState(false);
    const [items, setItems] = useState([]);

    const [state, setState] = useState(null);
    const [locations, setLocations] = useState([])
    // console.log(count)
    let loadLocations = (e) => {
        e.preventDefault();

    }

    useEffect(() => {
        fetch("/api/merchant/all/states/")
            .then(res => res.json())
            .then((result) => {
                setIsLoaded(true);
                setItems(result);
            },
                (error) => {
                    setIsLoaded(true);
                    setError(error);
                }
            )
        if (state != null) {
            fetch("/api/merchant/locations/2/")
                .then(res => res.json())
                .then((result) => {
                    setIsLoaded(true);
                    setLocations(result);
                },
                    (error) => {
                        setIsLoaded(true);
                        setError(error);
                    }
                )
        }


    }, [])


    return (
        <div>
            <form onChange={loadLocations}>
                <label for="states">Choose a State:</label>
                <select id="states" name="states" onChange={(e) => setState(e.target.value)}>
                    <option value="">Select State</option>

                    {items.map(states => (

                        <option key={states.id} value={states.id}>{states.name}</option>
                    ))}

                </select>


                <label for="location">Choose a Location:</label>
                <select id="location" name="location" onChange={(e) => setLocation(e.target.value)}>
                    <option value="">Select Location</option>

                    {Location.map(location => (

                        <option key={location.id} value={location.id}>{location.name}</option>
                    ))}

                </select>

            </form>

        </div>
    )
}

export default NullView

Solution

  • You can break the two fetches into separate useEffect callbacks. The first useEffect callback fetches on component mount and updates the "state" options while the second useEffect has a dependency on the currently selected "state" value.

    useEffect(() => {
      fetch("/api/merchant/all/states/")
        .then((res) => res.json())
        .then((result) => setItems(result))
        .catch((error) => setError(error))
        .finally(() => setIsLoaded(true));
    }, []); // <-- fetch once when component mounts
    
    useEffect(() => {
      if (state) {
        fetch(`/api/merchant/locations/${state}`) // <-- compute request URL
          .then((res) => res.json())
          .then((result) => setLocations(result))
          .catch((error) => setError(error))
          .finally(() => setIsLoaded(true));
      }
    }, [state]); // <-- fetch when state updates
    

    I believe you've also a typo in your render, Location should likely be locations state.

    return (
      <div>
        <form onChange={loadLocations}>
          <label for="states">Choose a State:</label>
          <select
            id="states"
            name="states"
            onChange={(e) => setState(e.target.value)}
          >
            <option value="">Select State</option>
            {items.map((states) => (
              <option key={states.id} value={states.id}>
                {states.name}
              </option>
            ))}
          </select>
    
          <label for="location">Choose a Location:</label>
          <select
            id="location"
            name="location"
            onChange={(e) => setLocation(e.target.value)}
          >
            <option value="">Select Location</option>
            {locations.map((location) => ( // <-- render locations state
              <option key={location.id} value={location.id}>
                {location.name}
              </option>
            ))}
          </select>
        </form>
      </div>
    );