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

Upvotes: 1

Views: 6492

Answers (2)

Eka Cipta
Eka Cipta

Reputation: 229

try this

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([])
    //i set api location in state
    const [apiLocation, setAPILocation] = useState(`/api/merchant/locations/`)
    // 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);
                }
            )
        fetch(apiLocation)
            .then(res => res.json())
            .then((result) => {
                setIsLoaded(true);
                setLocations(result);
            },
                (error) => {
                    setIsLoaded(true);
                    setError(error);
                }
            )


    }, [])

    checkUrlLocation(url, newValue) {
      let splited = url.split('/')
      splited[splited.length - 1] = newValue
      return splited.join('/')

    }

    return (
        <div>
            <form onChange={loadLocations}>
                <label for="states">Choose a State:</label>
                <select id="states" name="states" onChange={(e) =>{
                    /* in this case, if you select i states, it will put state id in api location and re-run useEffect */
                    setAPILocation(checkUrlLocation(apiLocation,e.target.value))
                    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 => (

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

                </select>

            </form>

        </div>
    )
}

export default NullView

Upvotes: 0

Drew Reese
Drew Reese

Reputation: 202605

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

Upvotes: 2

Related Questions