1

I have an array of objects

const array = [
  { itemId: "optionOne", name: "Option One", quantity: 10 },
  { itemId: "optionTwo", name: "Option Two", quantity: 20 },
  { itemId: "optionThree", name: "Option Three", quantity: 30 }
];

I need to update just the quantities using input box and then update state. end with something like this enter image description here

whats the best way to do this.

PLEASE find attached code example ready to modify handleChange() function CodeSandbox

Hman
  • 393
  • 2
  • 13
  • For starters, your array needs to not be defined in your `render()` function, otherwise it's going to overwrite back to the default values every time it renders – mhodges Nov 07 '19 at 21:35
  • @mhodges ah in CodeSandbox, its just an example. array is actually coming from an api. which I use to map over to create input options – Hman Nov 07 '19 at 21:38
  • Are you storing that in the component state? You say you want to "update state", but nothing in the sandbox is using state. Can you update the sandbox to recreate your use-case a little more accurately? Otherwise its hard to tell exactly what you're wanting to accomplish – mhodges Nov 07 '19 at 21:40
  • @mhodges Please check now. result comes back from api, and is set to state an passed down to chld component as prop. – Hman Nov 07 '19 at 21:45

2 Answers2

2

As mentioned in the comments, you should be storing your data in the state if you want to edit it. You have a decent start feeding in the index and value to the change function. Then you need to duplicate the array to prevent direct state mutation, alter the value accordingly and use setState to register the changes with React.

Fork here.

Chris B.
  • 5,477
  • 1
  • 12
  • 30
  • Since you mentioned immutability, it might be worth noting that `Array.from()` is not a deep copy of the array and therefore the objects themselves (on which you are mutating the quantity) are the original objects. – mhodges Nov 07 '19 at 22:03
  • Thanks Mate, you solution works but return string "1" instead of just number 1 – Hman Nov 07 '19 at 22:23
  • Text inputs always stringify everything. @mhodges this is true, I was simply pointing out the steps I took and why you wouldn't edit state directly. – Chris B. Nov 08 '19 at 06:23
  • 1
    @ChrisB. Yeah, understandable. I just wanted to clarify that without cloning each object, you **are** editing state directly. Those objects are the same ones that live in the array in state. Again, it's probably not a huge deal for the OP, but something worth mentioning for future readers so they don't get confused about mutability of objects that are nested within state – mhodges Nov 08 '19 at 19:59
2

If you want to store and update state, you need to add your data to the component state. This can be done in the constructor, and then modified using setState() in any of your component methods (i.e. if you are getting the data from an API, or modifying with your change handler). The component would look something like this:

constructor(props) {
  super(props);
  // set initial state, set to { array: [] } if getting data from API
  this.state = {
    array: [
      { itemId: "optionOne", name: "Option One", quantity: 10 },
      { itemId: "optionTwo", name: "Option Two", quantity: 20 },
      { itemId: "optionThree", name: "Option Three", quantity: 30 }
    ]
  };
}
handleChange = (itemIndex, itemValue) => {
  console.log(itemIndex, itemValue);
  // set state to changed values
  this.setState({
    array: this.state.array.map((item, index) => {
      // update item quantity (or clone & mutate if you need to maintain immutability)
      if (index === itemIndex) { item.quantity = +itemValue };
      return item;
    })
  });
};
render() {
  return (
    <div>
      {this.state.array.map((item, key) => {
        return (
          <FormGroup controlId="siteVisit">
            <Row>
              <Col xs={12} md={4}>
                <h3>{item.name}</h3>
              </Col>
              <Col xs={12} md={8}>
                <FormControl
                  type="text"
                  key={item.itemId}
                  onChange={item => {
                    this.handleChange(key, item.target.value);
                  }}
                />
                <span>{item.quantity}</span>
              </Col>
            </Row>
          </FormGroup>
        );
      })}
    </div>
  );
}

Edit

While the above solution is relatively simple, depending on your app structure, it might make more sense to use an "event emitter"-like function that you pass through that gets called instead. That way, there is no need to store or update state inside your child component - it becomes stateless and purely presentational. All of the data mutations/updates will happen on the parent and simply be passed down to the child for display. The main reason for wanting to do this is if the data changes and new data gets sent down from the parent. If you're not listening/reacting to changes in the child props, those changes will never trigger updates to your child component unless you explicitly listen for the changes, in which case, you're duplicating code. Anyway, might be overkill for this simple example, but it's something worth thinking about.

mhodges
  • 10,938
  • 2
  • 28
  • 46
  • Mate, this work perfectly. thanks. just wanted to ask how can I only return or filter down to itemId and quantity. once I have quantity I then make post request with these two. – Hman Nov 07 '19 at 22:22
  • @Hman Hmm, I'm not sure what you're asking. What exactly do you want to filter? `this.state.array`? Or do you want to filter another list based on the values of a given item in `this.state.array`? Or do you want to strip off all properties of the items in `this.state.array` except itemId and quantity for sending with post request? Can you please clarify? – mhodges Nov 07 '19 at 22:41
  • `strip off all properties of the items in this.state.array except itemId and quantity and their values`, spot on mate – Hman Nov 07 '19 at 23:05
  • @Hman Ah, that should be pretty easy with [`Array.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). `let strippedItems = this.state.array.map(({itemId, quantity}) => ({itemId, quantity}));` would be the shorthand way to do it. If you wanted to be more explicit, you could say `let strippedItems = this.state.array.map(item => { return {itemId: item.itemId, quantity: item.quantity}; });` They do the same thing – mhodges Nov 07 '19 at 23:23
  • 1
    perfect, Thanks Mate – Hman Nov 08 '19 at 00:37
  • I am not able to add quantities like 1.5 or 2.5, can only add 15, 25. how can i do that, thanks – Hman Nov 25 '19 at 01:16