Rohit Verma
Rohit Verma

Reputation: 3785

How to populate State dropdown data as per selected Country dropdown two times in a single component in React.js?

I want to populate state data as per selected country. This is working fine.

But I have two use this condition multiple times in a single page. How can I do this?

Issue Screenshot attached:- enter image description here

Sandbox Url:- https://codesandbox.io/s/country-state-sibling-issue-rdphoc?file=/src/App.js

My Code:-

import React, { useState, useEffect } from "react";
import "./styles.css";
import { TextField, MenuItem } from "@mui/material";

export default function App() {
  const body = [
    {
      state_ID: 1,
      state: "Delhi",
      country_ID: 1,
      country_name: "India"
    },
    {
      state_ID: 2,
      state: "Mumbai",
      country_ID: 1,
      country_name: "India"
    },
    {
      state_ID: 3,
      state: "Calgary",
      country_ID: 2,
      country_name: "Canada"
    },
    {
      state_ID: 4,
      state: "Toronto",
      country_ID: 2,
      country_name: "Canada"
    }
  ];
  const [country, setCountry] = useState([]);
  const [state, setState] = useState([]);
  const [selectedCountry, setSelectedCountry] = useState("");

  useEffect(() => {
    const uniqValues = [
      ...new Map(body.map((item) => [item["country_name"], item])).values()
    ];
    setCountry(uniqValues);
    setState(body);
  }, []);

  useEffect(() => {
    const newStates = body.filter(
      ({ country_name }) => country_name === selectedCountry
    );
    console.log(selectedCountry, newStates);
    setState(newStates);
  }, [selectedCountry]);

  useEffect(() => {}, [body, country]);

  return (
    <>
      <TextField
        className="ruleContainer"
        select
        name="Country"
        label="Country"
        variant="outlined"
        size="small"
        onChange={(event) => setSelectedCountry(event.target.value)}
      >
        {country
          ? country.map((opt) => (
              <MenuItem
                key={opt.country_name}
                value={opt.country_name}
                onChange={(value) => setSelectedCountry(value)}
              >
                {opt.country_name}
              </MenuItem>
            ))
          : ""}
      </TextField>
      <TextField
        className="ruleContainer"
        select
        name="state"
        label="State"
        variant="outlined"
        size="small"
        value=""
      >
        {state
          ? state.map((opt) => (
              <MenuItem key={opt.state} value={opt.state}>
                {opt.state}
              </MenuItem>
            ))
          : ""}
      </TextField>
      <hr />

      <TextField
        className="ruleContainer"
        select
        name="Country"
        label="Country"
        variant="outlined"
        size="small"
        onChange={(event) => setSelectedCountry(event.target.value)}
      >
        {country
          ? country.map((opt) => (
              <MenuItem
                key={opt.country_name}
                value={opt.country_name}
                onChange={(value) => setSelectedCountry(value)}
              >
                {opt.country_name}
              </MenuItem>
            ))
          : ""}
      </TextField>
      <TextField
        className="ruleContainer"
        select
        name="state"
        label="State"
        variant="outlined"
        size="small"
        value=""
      >
        {state
          ? state.map((opt) => (
              <MenuItem key={opt.state} value={opt.state}>
                {opt.state}
              </MenuItem>
            ))
          : ""}
      </TextField>
      <hr />
    </>
  );
}

Thanks for your efforts!

Upvotes: 3

Views: 1584

Answers (3)

Sergio Schirmer
Sergio Schirmer

Reputation: 301

You need to manage an array of objects in your state. Here's a good resource that explains how to do it: Managing State Array. I hope it helps!

Upvotes: 1

Dhaval Gajjar
Dhaval Gajjar

Reputation: 2925

To achieve multiple same forms with country and state dependent. You need to create the custom component with those form fields and maintain the state in that. So it will not affect the main component state.

enter image description here

You can find a working sample at https://codesandbox.io/s/country-state-sibling-issue-forked-9wcmi9?file=/src/App.js

CountryStateFormItems component

import { useState, useEffect } from "react";
import { TextField, MenuItem, Box } from "@mui/material";

const body = [
  {
    state_ID: 1,
    state: "Delhi",
    country_ID: 1,
    country_name: "India"
  },
  {
    state_ID: 2,
    state: "Mumbai",
    country_ID: 1,
    country_name: "India"
  },
  {
    state_ID: 3,
    state: "Calgary",
    country_ID: 2,
    country_name: "Canada"
  },
  {
    state_ID: 4,
    state: "Toronto",
    country_ID: 2,
    country_name: "Canada"
  }
];

export default function CountryStateFormItems(props) {
  const [country, setCountry] = useState([]);
  const [state, setState] = useState([]);
  const [selectedCountry, setSelectedCountry] = useState(props.form.country);
  const [selectedState, setSelectedState] = useState(props.form.state);

  useEffect(() => {
    const uniqValues = [
      ...new Map(body.map((item) => [item["country_name"], item])).values()
    ];
    setCountry(uniqValues);
  }, []);

  useEffect(() => {
    const newStates = body.filter(
      ({ country_name }) => country_name === selectedCountry
    );
    setState(newStates);
  }, [selectedCountry]);

  useEffect(() => {
    props.updateForm(props.index, selectedCountry, selectedState);
  }, [selectedState]);

  return (
    <>
      <Box display="flex">
        <TextField
          style={{ flex: 1 }}
          className="ruleContainer"
          select
          name="Country"
          label="Country"
          variant="outlined"
          size="small"
          value={selectedCountry}
          onChange={(event) => setSelectedCountry(event.target.value)}
        >
          {country
            ? country.map((opt) => (
                <MenuItem
                  key={opt.country_name}
                  value={opt.country_name}
                  onChange={(value) => setSelectedCountry(value)}
                >
                  {opt.country_name}
                </MenuItem>
              ))
            : ""}
        </TextField>
        &nbsp;
        <TextField
          style={{ flex: 1 }}
          className="ruleContainer"
          select
          name="state"
          label="State"
          variant="outlined"
          size="small"
          value={selectedState}
          onChange={(event) => setSelectedState(event.target.value)}
        >
          {state
            ? state.map((opt) => (
                <MenuItem
                  key={opt.state}
                  value={opt.state}
                  onChange={(value) => setSelectedState(value)}
                >
                  {opt.state}
                </MenuItem>
              ))
            : ""}
        </TextField>
      </Box>
      <hr />
    </>
  );
}

App component

import React, { useState, useCallback } from "react";
import "./styles.css";
import { Button } from "@mui/material";
import CountryStateFormItems from "./CountryStateFormItems";

export default function App() {
  const [forms, setForms] = useState([]);

  const addForm = () => {
    const existingForms = [...forms];
    existingForms.push({
      country: "",
      state: ""
    });
    setForms(existingForms);
  };

  const updateForm = useCallback(
    (index, country, state) => {
      const existingForms = [...forms];
      existingForms[index].country = country;
      existingForms[index].state = state;
      setForms(existingForms);
    },
    [forms, setForms]
  );

  const printForm = () => {
    console.log(forms);
  };

  return (
    <>
      <Button variant="contained" onClick={addForm}>
        Add
      </Button>
      &nbsp;
      <Button variant="contained" onClick={printForm}>
        Print
      </Button>
      <hr />
      {forms
        ? forms.map((form, index) => (
            <CountryStateFormItems
              key={index}
              index={index}
              form={form}
              updateForm={updateForm}
            />
          ))
        : ""}
    </>
  );
}

Upvotes: 4

Jatin Ranka
Jatin Ranka

Reputation: 35

You can use two state variables for storing both countries. Eg:

const [selectedCountry1, setSelectedCountry2] = useState("");
const [selectedCountry2, setSelectedCountry2] = useState("");

And similarly, you can use 2 variables for storing the country's state values as well.


Alternatively, you can also store it in a single state object with multiple keys, something like:

const [selectedCountriesObject, setSelectedCountriesObject] = useState({ country1: "", country2: ""});

Upvotes: 2

Related Questions