SAYAN
SAYAN

Reputation: 35

Cannot update particular property of object in react js using setState

I am fetching data and I have mapped the data to display it. Everything is fine but when I try to update the fetched data (which I have already stored in an array, just want to change it through other way , mentioned below) does not update.I have given the data below,I just want to change the 'quantity' attribute of each object respectively when I click the button but everytime I try to use the spread operator and give a comma and try to define the attribute of the mapped objects which is 'quantity' here, it doesnot update the quantity attribute and replaces the whole array.Can you please tell me how to write a onClick function which copies the whole array(using spread operator) and then update the quantity attribute.(the fetched data is stored in an array)

0: {id: 11, ordered: true, quantity: 5, user: 1, item: {…}}
1: {id: 12, ordered: true, quantity: 1, user: 1, item: {…}}
2: {id: 14, ordered: true, quantity: 1, user: 1, item: {…}}
3: {id: 15, ordered: true, quantity: 4, user: 1, item: {…}}
4: {id: 17, ordered: true, quantity: 3, user: 1, item: {…}}

Here is the data I am fetching.(above)

0:
    id: 11
    item: {id: 10, title: "Watch", image: "https://github.com/divanov11/django_ecommerce_mod5/blob/master/static/images/watch.jpg?raw=true", price: "$20"}
    ordered: true
    quantity: 5
    user: 1

It is the data in each object(above).I want to keep everything same and just change the quantity attribute of each object respectively.Below is my code.

import React, { useEffect, useState } from 'react';

let Cart = () => {

    let [myItems, setMyItems] = useState([])

    let fetchData = () => {
        fetch('http://127.0.0.1:8000/api/get-order-item/' + sessionStorage.getItem('id'), {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer' + ' ' + sessionStorage.getItem('token')
            }
        }).then(
            (response) => {
                return response.json()
            }
        ).then(
            (data) => {
                if (data) {
                    setMyItems(data)
                }
            })
    }

    useEffect(
        () => {
            fetchData()
            console.log(myItems)
        }, []
    )



    return (
        <div>
            {
                myItems.map(
                    (value) => {
                        return (
                            <div className='row  w-100 m-auto' >
                                <div className="card-deck container-fluid m-auto bg-light">
                                    <div className="card  bg-light mt-4 mb-2 " >
                                        <img src={value.item.image} className="card-img-top" alt="..." />
                                        <div className="card-body border-top ">
                                            <div className='d-flex justify-content-between' >
                                                <h4>{value.item.title}</h4>
                                                <div className='d-flex flex-row-reverse' >
                                                    <svg onClick={ () => { setMyItems( (prevState) => { return [...prevState , {value : { quantity : prevState.quantity - 1 }}  ] } )  } } id='add' xmlns="http://www.w3.org/2000/svg" width="28" height="23" fill="currentColor" class="bi bi-plus" viewBox="0 0 16 16" >
                                                        <path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
                                                    </svg>
                                                    <input defaultValue={value.quantity} className='w-25 h-75 text-center' />
                                                    <svg onClick={() => { console.log(myItems) }} xmlns="http://www.w3.org/2000/svg" width="32" height="25" fill="currentColor" class="bi bi-dash" viewBox="0 0 16 16">
                                                        <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z" />
                                                    </svg>
                                                </div>
                                            </div>
                                            <h5>{value.item.price}</h5>
                                            <div class="form-check d-flex  justify-content-between">
                                                <input type="checkbox" class="form-check-input" id="quantity" checked={value.ordered} />
                                                <label class="form-check-label" for="exampleCheck1">Proceed this item to checkout</label>
                                                <button type="button" class="btn btn-success ">Save changes</button>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div >
                        )
                    }
                )
            }
        </div >
    )


}

export default Cart;

Inside the map function , where I am mapping the objects , I just want to create a button which changes the quantity attribute of their respectively objects

Upvotes: 4

Views: 1205

Answers (1)

ggorlen
ggorlen

Reputation: 57344

The attempt is:

setMyItems( (prevState) => { return [...prevState , {value : { quantity : prevState.quantity - 1 }}  ] } )  } }

Firstly, this is pretty hard to read inline, so I'd move this to a function so you can understand what you're working with.

Next, instead of ...prevState, which spreads the entire previous array and appends a new tail with {value : ...}, find the item you want to mutate by index or id (sort of an application-specific decision--probably id is better) and slice the array into three pieces:

  • The front of the array from 0 to i
  • The item we want to change the quantity of, i
  • The back of the array from i + 1 to the end

This can be done with slicing and spreading to build a new array:

const newArr = [...arr.slice(0, i), arr[i], ...arr.slice(i + 1)];

Last step: manipulating the arr[i] object to adjust the quantity. It looks like quantity is on the top-level, so:

{...arr[i], quantity: newQuantity}

Putting it all together, here's a minimal, runnable example you can apply to your code:

<script type="text/babel" defer>
const {useEffect, useState} = React;

const mockItems = [
  {id: 11, quantity: 5, item: {title: "foo"}},
  {id: 12, quantity: 1, item: {title: "bar"}},
  {id: 14, quantity: 1, item: {title: "baz"}},
  {id: 15, quantity: 4, item: {title: "quux"}},
  {id: 17, quantity: 3, item: {title: "corge"}},
];
fetch = () => new Promise((resolve, reject) => 
  setTimeout(() => 
    resolve({json: async () => mockItems})
  , 1000)
);

const Cart = () => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    (async () => {
      const response = await fetch("some endpoint");
      setItems(await response.json());
    })();
  }, []);
  
  const setQuantity = (id, quantity) => {
    const i = items.findIndex(e => e.id === id);
    setItems(prev => [
      ...prev.slice(0, i),
      {...prev[i], quantity},
      ...prev.slice(i + 1),
    ]);
  };

  return (
    <ul>
      {!items.length ? "loading..." :
        items.map(e =>
          <li key={e.id}>
            <div>{e.item.title}</div>
            <div>
              Qty: {e.quantity}
              <button 
                onClick={() => 
                  setQuantity(e.id, e.quantity + 1)
                }
              >+</button>
              <button 
                onClick={() =>
                  setQuantity(e.id, e.quantity - 1)
                }
              >-</button>
            </div>
          </li>
        )
      }
    </ul>
  );
};

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<Cart />);

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

Upvotes: 2

Related Questions