4

Given my state looks like so:

cart: [
  { id: 1, name: 'apple', price: 100, quantity: 1 }
]

How do I setState of that specific object's quantity property ?

3 Answers 3

4

You can get the indexOf the item in the cart which you want to update, and update the cart with everything up to that item, your updated item, and the rest of the items.

Example

class App extends React.Component {
  state = {
    cart: [
      { id: 1, name: "apple", price: 100, quantity: 1 },
      { id: 2, name: "orange", price: 50, quantity: 10 },
      { id: 3, name: "banana", price: 20, quantity: 100 }
    ]
  };

  increaseQuantity = item => {
    this.setState(previousState => {
      const { cart } = previousState;
      const itemIndex = cart.indexOf(item);

      return {
        cart: [
          ...cart.slice(0, itemIndex),
          { ...cart[itemIndex], quantity: cart[itemIndex].quantity + 1 },
          ...cart.slice(itemIndex + 1)
        ]
      };
    });
  };

  render() {
    return (
      <div>
        {this.state.cart.map(item => (
          <div key={item.id} onClick={() => this.increaseQuantity(item)}>
            {item.name} {item.quantity}
          </div>
        ))}
      </div>
    );
  }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Nice answer. Any reason you do {cart} here?
Thanks @Chris. Just in case the updates to quantity comes in very frequently, there could be race conditions where the state is not updated properly unless the cart is taken from the previous state in the setState callback.
@Tholle That can never happen from a single event handler because of how React batches setState calls during an event. That said, I always encourage the use of functional setState when the new state depends on the previous one. Anyway, In my comment though, I was wondering why you are destructuring cart from previousState rather than using it directly.
Functional setState rules ;) No, I was just wondering if you did it to shorten your code or for other reasons. You answered my question now though.
just to add a quick note here, its not recommended to have an inline lambda in the render as that creates a new function each render cycle which isn't performant and can cause additional re renders on child elements. its best to have these functions on the class, or child components. if you stick with the inline function approach at least use .bind instead of () => {} as bind is about twice as performant. Happy coding! :)
|
3

You can update your cart as

updateCart(id, itemAttributes) {
  var index = this.state.cart.findIndex(x=> x.id === id);
  if (index === -1)
    // handle error
  else
    this.setState({
      cart: [
         ...this.state.cart.slice(0, index),
         Object.assign({}, this.state.cart[index], itemAttributes),
         ...this.state.cart.slice(index+1)
      ]
    });
}

then call your updateCart function with id of your cart item as

this.updateCart(1, {quantity: 2});

2 Comments

This is nice, but why use spread in two places, but Object.assign in the other? Wouldn't it be better to do spread on all three?
@Chris spread will also work here. Object.assign can mutate the original object which is sometimes useful. spread cannot.
2
 this.setState(prev => ({
   cart: [{ ...prev.cart[0], quantity: 10, }].concat(prev.cart.slice(1))
 }));

Copy the array and replace the object you wanna change with a copied version. If you do that often you may use some utilities:

const replace = index, replacer) => arr =>
   arr.map((el, i) => i === index ? replacer(el) : el);
const key = (k, replacer)  => state => ({...state, [k]: replacer(state[k]) });

Usable as:

this.setState(key(
   "cart", 
   replace(
     0, 
     key("quantity", () => 10)
   )
));

Comments

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.