Search code examples
reactjsstaterecoiljs

How to change property of object in React, Recoil


I'm working on a shopping cart.
<Cart /> is Cart Page which render Products in cartList Array.
<CartProduct /> render each one product in cartList Array

enter image description here

I want to make the quantity data change, when i click quantity button.

Here is My First Try Code

function Cart(props) {
    const cartsList = useRecoilValue(cartsListState);

    return(
        {cartsList
           .filter(cart => cart.keep === 'cold')
           .map((cart) => {
                return <CartProduct cart={cart} getNowQuantity={getNowQuantity} />
            })
         }
       {cartsList
            .filter(cart => cart.keep === 'freeze')
            .map((cart) => {
                return <CartProduct cart={cart} getNowQuantity={getNowQuantity} />
              })
        }
       {cartsList
            .filter(cart => cart.keep === 'normal')
            .map((cart) => {
                 return <CartProduct cart={cart} getNowQuantity={getNowQuantity} />
             })
        }
     )
}   

function CartProduct({ cart, getNowQuantity}) {
    const [cartsList, setCartsList] = useRecoilState(cartsListState);
    
    return(
        const PlusQuantity = () => {
            setCounterQuantity(counterQuantity => counterQuantity + 1);
            cart.quantity += 1;
            getNowQuantity(cart.quantity);
    }

    const MinusQuantity = () => {
        if (cart.quantity>=2) {
            setCounterQuantity(counterQuantity => counterQuantity - 1);
            cart.quantity -= 1;
            getNowQuantity(cart.quantity);
        }
        else return;
    }
    )
}

Firts code make error

Uncaught TypeError: Cannot assign to read only property 'quantity' of object '#

So i tried to use spread operator in CartProduct.js Like this way

const cartProduct = ([ ...cart]);
 
return(
   CartProduct.quantity = +1 
~~
~~~
)

This code make error

cart is not iterable

so i tried

let iterableCart = cart[Symbol.iterator]

It doesn't work.

How can i change cart.property for ChangeQuantityButton?


Solution

  • By default Recoil makes everything immutable so code like this

    cart.quantity += 1;
    

    won't work because you're trying to update a value on a frozen object.

    Instead you need to create a new object, and use the existing values to update it.

    Here I'm using a sample data set and using it to build three product items using an <Item> component. This component displays the name, the current quantity, and two buttons to decrement/increment the values. On the buttons are a couple of data attributes that identify the product id, and the button action.

    When a button is clicked the handleClick function is called. This destructures the id and action from the button dataset, and then map over the current cart using those values to check the cart items and return updated objects to the state.

    const { atom, useRecoilState, RecoilRoot } = Recoil;
    
    // Initialise the cart data
    const cart = [
      { id: 1, name: 'Banana', qty: 0 },
      { id: 2, name: 'Beef', qty: 0 },
      { id: 3, name: 'Mop', qty: 0 }
    ];
    
    // Initialise the cart atom setting its
    // default to the cart data
    const cartAtom = atom({
      key: 'cartAtom',
      default: cart
    });
    
    function Example() {
    
      // Use the recoil state
      const [ cart, setCart ] = useRecoilState(cartAtom);
    
      // When a button is clicked
      function handleClick(e) {
    
        // Get its id and action
        const { dataset: { id, action } } = e.target;
    
        // Update the cart using the id to identify the item
        // and return an updated object where appropriate
        setCart(prev => {
          return prev.map(item => {
            if (item.id === +id) {
              if (action === 'decrement' && item.qty > 0) {
                return { ...item, qty: item.qty - 1 };
              }
              if (action === 'increment') {
                return { ...item, qty: item.qty + 1 };
              }
            }
            return item;
          });
        });
    
      }
    
      // Iterate over the cart data using the Item
      // component to display the item details
      return (
        <div>
          {cart.map(item => {
            const { id, name, qty } = item;
            return (
              <Item
                key={id}
                id={id}
                name={name}
                qty={qty}
                handleClick={handleClick}
              />
            );
          })}
        </div>
      );
    
    }
    
    function Item({ id, name, qty, handleClick }) {
      return (
        <div className="item">
          <span className="name">{name}</span>
          <button
            data-id={id}
            data-action="decrement"
            type="button"
            onClick={handleClick}
          >-
          </button>
          {qty}
          <button
            data-id={id}
            data-action="increment"
            type="button"
            onClick={handleClick}
          >+
          </button>
        </div>
      );
    }
    
    ReactDOM.render(
      <RecoilRoot>
        <Example />
      </RecoilRoot>,
      document.getElementById('react')
    );
    .item:not(:last-child) { margin-bottom: 0.25em; }
    .name { display: inline-block; width: 60px; }
    button { width: 30px; height: 30px; margin: 0 0.25em; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/index.js"></script>
    <div id="react"></div>