1

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 ********* Adding an initial service Outcome of the initial service addition


imgSet-2 ********* Adding the second service Outcome of the second service addition


imgSet-3 ********* 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.}
]
}
1
  • Please provide numServices detail. How do you get and fill? Commented Jan 12, 2023 at 1:31

1 Answer 1

0

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

Sign up to request clarification or add additional context in comments.

12 Comments

TypeError: (intermediate value)(intermediate value)(intermediate value) is not iterable
That is the error I get when I try to add anything.
@mattr534 I think that happens because of the value property not working properly, I updated my answer
It still is not working. I believe it's something to do with the initial value which is just null.
So every time I add to the estimates it ends up being an empty array. This is the result { "estimate": { "items": [] } }
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.