Home > database >  I'm trying to add to an array of objects that is broken into two inputs in React
I'm trying to add to an array of objects that is broken into two inputs in React

Time:01-18

So I have an array of objects where the keys are 'cost' and 'service' called estimate. You can add to the array by clicking 'Add' which adds a new index (i) to the array. The issue is on the first cycle I get a good array of {'cost': 2500, 'service': "commercial cleaning"} (imgSet-1) but when I add another item it completely erases the array and sets only one of the nested objects key and value. (imgSet-2). This is the outcome I'm looking for once the state has been saved (imgSet-3) I have tried going with @RubenSmn approach but then I receive this error. (imgSet-4)

imgSet-1 ********* Finale rendered outcome


imgSet-4 ********* Error after trying dif approach


Below is the code for the part of the page where you can add services and the output of the text inputs.

const [estimate, setEstimate] = useState([]);

{[...Array(numServices)].map((e, i) => {
            return (
              <div key={i} className="flex justify-between">
                <div>
                  <NumericTextBoxComponent
                    format="c2"
                    name={`cost-${i}`}
                    value={estimate?.items?.["cost"]?.[i]}
                    change={(e) =>
                      setEstimate({ ...estimate, items: [{...estimate?.items?.[i],cost: e?.value}]})
                    }
                    placeholder='Price'
                    floatLabelType="Auto"
                    data-msg-containerid="errorForCost"
                  />
                </div>
                <div>
                  <DropDownListComponent
                    showClearButton
                    fields={{ value: "id", text: "service" }}
                    name={`service-${i}`}
                    value={estimate?.items?.["service"]?.[i]}
                    change={(e) =>
                      setEstimate({ ...estimate, items: [{...estimate?.items?.[i],service: e?.value}]})
                    }
                    id={`service-${i}`}
                    floatLabelType="Auto"
                    data-name={`service-${i}`}
                    dataSource={estimateData?.services}
                    placeholder="Service"
                    data-msg-containerid="errorForLead"
                  ></DropDownListComponent>
                  <div id="errorForLead" />
                </div>
              </div>
            );
          })}
        </form>
        <button onClick={() => setNumServices(numServices   1)}>Add</button>

I have tried multiple variations of spread operators but I can't seem to get it to work. My expected result would be:

estimate:{
  items: [
    {'cost': 2500, 'service': 'Commercial Clean'},
    {'cost': 500, 'service': 'Bathroom Clean'},
    {'cost': 180, 'service': 'Apartment Clean'},
    {etc.}
]
}

CodePudding user response:

The initial state is an array which is not the object you're setting in the change handlers. You can have an initial state like this.

const [estimate, setEstimate] = useState({ items: [] });

You're not adding back the old items of the state when you're setting the new state.

setEstimate({
  ...estimate,
  items: [{ ...estimate?.items?.[i], cost: e?.value }],
  // should be something like
  // items: [...estimate.items, { ...estimate.items?.[i], cost: e?.value }],
});

But you can't do that since it will create a new object in your items array every time you change a value.

I made this dynamic handleChange function which you can use for you state changes. The first if statement is to check if the itemIndex is already in the items array. If not, create a new item with the propertyName and the value

const handleChange = (e, itemIndex, propertyName) => {
  const newValue = e?.value;

  setEstimate((prevEstimate) => {
    if (prevEstimate.items.length <= itemIndex) {
      const newItem = { [propertyName]: newValue };
      return {
        ...prevEstimate,
        items: [...prevEstimate.items, newItem]
      };
    }

    // loop over old items
    const newItems = [...prevEstimate.items].map((item, idx) => {
      // if index' are not the same just return the old item
      if (idx !== itemIndex) return item;
      // else return the item with the new service
      return { ...item, [propertyName]: newValue };
    });

    return {
      ...prevEstimate,
      items: newItems,
    };
  });
};

For the Service dropdown, you can do the same for the Cost just change the property name

<DropDownListComponent
  ...
  value={estimate.items[i]?.service}
  change={(e) => handleChange(e, i, "service")}
  ...
></DropDownListComponent>

See here a simplified live version

  • Related