Home > OS >  I can't update state when submitted in a form
I can't update state when submitted in a form

Time:05-26

I need to update the state on main page, the problem is that I update values 3 levels down...

This component is where I get all the cities, putting the data on the State and map through cities.

CitiesPage.tsx

export const CitiesPage = () => {
  const [cities, setCities] = useState<City[]>([]);

  useEffect(() => {
    getCities().then(setCities);
  }, []);

  return (
    <>
      <PageTitle title="Cities" />
      <StyledCitySection>
        <div className="headings">
          <p>Name</p>
          <p>Iso Code</p>
          <p>Country</p>
          <span style={{ width: "50px" }}></span>
        </div>
        <div className="cities">
          {cities.map((city) => {
            return <CityCard key={city.id} city={city} />;
          })}
        </div>
      </StyledCitySection>
    </>
  );
};

On the next component, I show cities and options to show modals for delete and update.

CityCard.tsx.

export const CityCard = ({ city }: CityProps) => {
  const [showModal, setShowModal] = useState(false);

  const handleModal = () => {
    setShowModal(true);
  };

  const handleClose = () => {
    setShowModal(false);
  };

  return (
    <>
      {showModal && (
        <ModalPortal onClose={handleClose}>
          <EditCityForm city={city} closeModal={setShowModal} />
        </ModalPortal>
      )}
      <StyledCityCard>
        <p className="name">{city.name}</p>
        <p className="isoCode">{city.isoCode}</p>
        <p className="country">{city.country?.name}</p>
        <div className="options">
          <span className="edit">
            <FiEdit size={18} onClick={handleModal} />
          </span>
          <span className="delete">
            <AiOutlineDelete size={20} />
          </span>
        </div>
      </StyledCityCard>
    </>
  );
};

and finally, third levels down, I have this component.

EditCityForm.tsx.

export const EditCityForm = ({ city, closeModal }: Props) => {
  const [updateCity, setUpdateCity] = useState<UpdateCity>({
    countryId: "",
    isoCode: "",
    name: "",
  });

  const handleChange = (
    evt: ChangeEvent<HTMLInputElement | HTMLSelectElement>
  ) => {
    const { target } = evt;
    setUpdateCity({ ...updateCity, [target.name]: target.value });
  };

  const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
    evt.preventDefault();
    const { id: cityId } = city;
    updateCityHelper(cityId, updateCity);
    closeModal(false);
  };

  useEffect(() => {
    setUpdateCity({
      isoCode: city.isoCode,
      name: city.name,
      countryId: city.country?.id,
    });
  }, [city]);

  return (
    <form onSubmit={handleSubmit}>
      <Input
        label="Iso Code"
        type="text"
        placeholder="Type a IsoCode..."
        onChange={handleChange}
        name="isoCode"
        value={updateCity.isoCode}
      />
      <Input
        label="Name"
        type="text"
        placeholder="Paste a Name.."
        onChange={handleChange}
        name="name"
        value={updateCity.name}
      />
      <CountrySelect
        label="Country"
        onChange={handleChange}
        value={city.country?.name || ""}
        name="countryId"
      />
      <Button type="submit" color="green" text="Update" />
    </form>
  );
};

Edit form where retrieve data passed from CityCard.tsx and update State, passing data to a function that update Info, closed modal and... this is where I don't know what to do.

How can I show the info updated on CitiesPage.tsx when I submitted on EditCityForm.tsx

Any help will be appreciated.
Thanks!

CodePudding user response:

I'd advise you to use React-Redux or Context API whenever you have nested structures and want to access data throughout your app.

However in this case you can pass setCities and cities as a prop to CityCard and then pass this same prop in the EditCityForm component and you can do something like this in your handleSubmit.

const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
    evt.preventDefault();
    let updatedCities = [...cities];
    updatedCities.forEach(el => {
       if(el.id == updateCity.id) {
         el.countryCode = updateCity.countryCode;
         el.name = updateCity.name;
         el.isoCode = updateCity.isoCode;
       }
    })
    setCities(updatedCities);
    closeModal(false);
  };

CodePudding user response:

You are storing the updated value in a different state, namely the updateCity state, but what you should be doing is update the origional cities state. While these two states are not related, and at the same time your UI is depend on cities state's data, so if you wish to update UI, what you need to do is update cities' state by using it's setter function setCities.

Just like passing down state, you pass it's setters as well, and use the setter function to update state's value:

// CitiesPage
{cities.map((city) => {
    return <CityCard key={city.id} city={city} setCities={setCities} />;
})}

// CityCard
export const CityCard = ({ city, setCities }: CityProps) => {
    // ...

    return (
        // ...
        <ModalPortal onClose={handleClose}>
          <EditCityForm city={city} closeModal={setShowModal} setCities={setCities} />
        </ModalPortal>
        // ...
    )
}


// EditCityForm
export const EditCityForm = ({ city, closeModal, setCities }: Props) => {
  // const [updateCity, setUpdateCity] = useState<UpdateCity>({  // no need for this
  //   countryId: "",
  //   isoCode: "",
  //   name: "",
  // });  

  const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
    evt.preventDefault();
    const { id: cityId } = city;
    setCities(); // your update logic
    closeModal(false);
  };
}
  • Related