Home > Software engineering >  I want to update the states base on another state update (selectedCountry) using the 'onCountry
I want to update the states base on another state update (selectedCountry) using the 'onCountry

Time:07-24

basically I want to create dynamic select dropdown/select using react react bootstrap. Which is like Country, States and Cities.

as per my current code, base on setSelectedCountry I want to update setStates also. I want to update list of the states base on selected Country.

there's total 6 states are available, 3 for manage list and 3 for manage selected state.

How can I achieve this? please help me.

App.js

import { useEffect, useState } from 'react';
import RBContainer from './components/RBComponents/container/RBContainer'
import RBSelect from './components/RBComponents/select/RBSelect'
import data from './db.json';
import './App.scss';

function App() {
  const [countries, setCountries] = useState([])
  const [states, setStates] = useState([])
  const [cities, setCities] = useState([])

  const [selectedCountry, setSelectedCountry] = useState("Select Country")
  const [selectedState, setSelectedState] = useState("Select State")
  const [selectedCity, setSelectedCity] = useState("Select City")

  // initial set country select menu items
  useEffect(() => {
    setCountries(data)
  }, [])

  const onCountryChangeHandler = (e) => {
    console.log(e.target.value);
    setSelectedCountry(e.target.value)
    // setSelectedCountry((prv) => prv, e.target.value);
    onSelectCountry(e)
  }
  // setState as callback
  function onSelectCountry(e) {
    console.log('working...')
    countries.forEach(element => {
      if (element.countryId === parseInt(e.target.value)) {
        setStates((prv) => prv, element.states)
      }
      console.log(states);
    });
  }

  const onStateChangeHandler = (e) => {
    console.log(e.target.value);
  }

  return (
    <div className='app my-4'>
      <RBContainer>
        <RBSelect className='mb-4' value={selectedCountry} onChange={onCountryChangeHandler}>
          <option disabled>Select Country</option>
          {countries.map(country => (
            <option
              key={country.countryId}
              value={country.countryId}
            >
              {country.countryName}
            </option>
          ))}
        </RBSelect>
        <RBSelect value={selectedState} onChange={onStateChangeHandler}>
          <option disabled>Select State</option>
          {states.map(state => (
            <option
              key={state.stateId}
              value={state.stateId}
            >
              {state.stateName}
            </option>
          ))}
        </RBSelect>
      </RBContainer>
    </div>
  );
}

export default App;

this is my local json file.

db.json

[
  {
    "countryId": 1,
    "countryName": "India",
    "states": [
      {
        "stateId": 1,
        "stateName": "Gujarat",
        "cities": [
          {
            "cityId": 1,
            "cityName": "Ahmadabad"
          },
          {
            "cityId": 2,
            "cityName": "Surat"
          },
          {
            "cityId": 2,
            "cityName": "Baroda"
          }
        ]
      },
      {
        "stateId": 2,
        "stateName": "Maharashtra",
        "cities": [
          {
            "cityId": 3,
            "cityName": "Mumbai"
          },
          {
            "cityId": 4,
            "cityName": "Pune"
          }
        ]
      }
    ]
  },
  {
    "countryId": 2,
    "countryName": "USA",
    "states": [
      {
        "stateId": 3,
        "stateName": "California",
        "cities": [
          {
            "cityId": 5,
            "cityName": "Los Angeles"
          },
          {
            "cityId": 6,
            "cityName": "San Diego"
          }
        ]
      }
    ]
  }
]

CodePudding user response:

It's not a good idea to keep the actual lists of countries/states/etc in the react state. Theres no need, they are immutable. You only need to store the selected items, and then you can derive the actual lists from the JSON in a function, without storing them in new state unnecessarily.

You likely need to take a step back and refactor.

import { useEffect, useState } from 'react';
import RBContainer from './components/RBComponents/container/RBContainer'
import RBSelect from './components/RBComponents/select/RBSelect'
import data from './db.json';
import './App.scss';

function App() {
  const [selectedCountry, setSelectedCountry] = useState("Select Country");
  const [selectedState, setSelectedState] = useState("Select State");
  const [selectedCity, setSelectedCity] = useState("Select City");

  const onCountryChangeHandler = (e) => {
    setSelectedCountry(e.target.value);
  };

  const onStateChangeHandler = (e) => {
    setSelectedState(e.target.value);
  };

  const onCityChangeHandler = (e) => {
    setSelectedCity(e.target.value);
  };

  const country = data.find(
    (country) => country.countryId === parseInt(selectedCountry)
  );

  const state = country?.states?.find(
    (state) => state.stateId === parseInt(selectedState)
  );

  return (
    <div className="app my-4">
      <RBContainer>
        <RBSelect
          className="mb-4"
          value={selectedCountry}
          onChange={onCountryChangeHandler}
        >
          <option disabled>Select Country</option>
          {data.map((country) => (
            <option key={country.countryId} value={country.countryId}>
              {country.countryName}
            </option>
          ))}
        </RBSelect>
        <RBSelect value={selectedState} onChange={onStateChangeHandler}>
          <option disabled>Select State</option>
          {country?.states.map((state) => (
            <option key={state.stateId} value={state.stateId}>
              {state.stateName}
            </option>
          ))}
        </RBSelect>
        <RBSelect value={selectedCity} onChange={onCityChangeHandler}>
          <option disabled>Select City</option>
          {state?.cities.map((city) => (
            <option key={city.cityId} value={city.cityId}>
              {city.cityName}
            </option>
          ))}
        </RBSelect>
      </RBContainer>
    </div>
  );
}

export default App;


CodePudding user response:

Since you already have the list of countries and their child states and cities, you can access them directly using countries[selectedCountry] instead of storing each list of states and cities in your component's state.

import { useEffect, useState } from 'react';
import RBContainer from './components/RBComponents/container/RBContainer'
import RBSelect from './components/RBComponents/select/RBSelect'
import data from './db.json';
import './App.scss';

function App() {
  const [countries, setCountries] = useState([])
  // removed unneeded states and cities

  const [selectedCountry, setSelectedCountry] = useState("Select Country")
  const [selectedState, setSelectedState] = useState("Select State")
  const [selectedCity, setSelectedCity] = useState("Select City")

  // initial set country select menu items
  useEffect(() => {
    setCountries(data);
  }, [])

  const onCountryChangeHandler = (e) => {
    console.log(e.target.value);
    setSelectedCountry(e.target.value);
    // clear previous selections
    setSelectedState(null);
    setSelectedCity(null);
  }

  const onStateChangeHandler = (e) => {
    setSelectedState(e.target.value);
    // clear previous selection
    setSelectedCity(null);
  }

  const onCityChangeHandler = (e) => {
    setSelectedCity(e.target.value);
  }

  return (
    <div className='app my-4'>
      <RBContainer>
        <RBSelect className='mb-4' value={selectedCountry} onChange={onCountryChangeHandler}>
          <option disabled>Select Country</option>
          {countries.map(country => (
            <option
              key={country.countryId}
              value={country.countryId}
            >
              {country.countryName}
            </option>
          ))}
        </RBSelect>
        <RBSelect value={selectedState} onChange={onStateChangeHandler}>
          <option disabled>Select State</option>
          {/* Access states list of selected country */}
          {(countries[selectedCountry]?.states || []).map(state => (
              <option
                key={state.stateId}
                value={state.stateId}
              >
                {state.stateName}
              </option>
          ))}
        </RBSelect>
        {/* Added city select for example */}
        <RBSelect value={selectedCity} onChange={onCityChangeHandler}>
          <option disabled>Select City</option>
          {(countries[selectedCountry]?.states?.[selectedState]?.cities || []).map(city => (
              <option
                key={city.cityId}
                value={city.cityId}
              >
                {city.cityName}
              </option>
          ))}
        </RBSelect>
      </RBContainer>
    </div>
  );
}

export default App;
  • Related