Невозможно правильно обновить общее значение в состоянии - PullRequest
0 голосов
/ 30 мая 2018

Я пишу простую систему корзины покупок без избыточности.

Ожидаемое поведение : обновить общую сумму при изменении количества продуктов

Проблема :

  1. после первого рендеринга общая сумма составляет 0
  2. после удаления элемента общая сумма не изменяется

Мне нужно: элегантное решение.Не жесткий код.

const products = [
  {
  	id: "1",
    title: "item 1",
    price: "2450",
    left: "14",
    quantity: "1"
  },
  {
  	id: "2",
    title: "item 2",
    price: "2450",
    left: "178",
    quantity: "1"
  },
  {
  	id: "3",
    title: "item 3",
    price: "2450",
    left: "1",
    quantity: "1"
  },
  {
  	id: "4",
    title: "item 4",
    price: "2450",
    left: "12",
    quantity: "1"
  }
];

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: 0
    };
    
    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );
  }
  
  
  handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
    	return product.id != id;
    } );
    
    this.setState( {
    	cart
    } );
    
    this.sumTotalAmount();
  }
  
  handleChangeQuantity( e, id ) {
    let cart = this.state.cart;
    cart = cart.map( ( product ) => {
    	if (product.id == id ) {
      	product.quantity = e.target.value;
      }
     
    	return product;
    } );
    
    this.setState( {
    	cart
    } );
    this.sumTotalAmount();
  }
  
  sumTotalAmount() {
  	let cart = this.state.cart;
    let totalAmount = cart.map( ( product ) => {
    	return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current );
    
    this.setState( {
    	totalAmount
    } );
  }

  render() {
    return(
      <div>
        <div className="cart">
          {
            this.state.cart.map( ( item, index ) => {
              return(
                <Product key={index} item={item}
                handleRemoveProduct={this.handleRemoveProduct}
                handleChangeQuantity={this.handleChangeQuantity}
                />
              )
            })
          }
        </div>
        <div className="cart__total">    	
          Total amount - {this.state.totalAmount}
        </div>
      </div>
    )
  }
}

const Product = ( props ) => (
  <div className="cart__product">
    {props.item.title} <a href="#" onClick={() => props.handleRemoveProduct(props.item.id)}>Remove</a>
     <input type="number" name="quantity" min="1" max={props.item.left} value={props.item.quantity} onChange={(e) => props.handleChangeQuantity(e, props.item.id)}/>
  </div>
);

ReactDOM.render(<App products = {products} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

Ответы [ 4 ]

0 голосов
/ 30 мая 2018

Метод setState является асинхронным, поэтому this.sumTotalAmount() почти ничего не сделает.У вас есть как минимум три способа исправить ваш код:

  • передать функцию суммы в качестве параметра обратного вызова в setState (и вызвать setState в componentDidMount)

    this.setState({
        cart: newCart
    }, () => {
        this.sumTotalAmount();
    })
    
  • сделать функцию суммы чистой и вызвать setState с новым состоянием корзины и вычисленной суммой (вы в основном кешируете сумму непрозрачно в состоянии)

    this.setState({
        cart: newCart,
        sum: calculateSum(newCart),
    })
    
  • сделайте функцию суммы чистой и используйте ее только тогда, когда вам нужно значение (если вы использовали памятку, в этом случае вы бы кэшировали сумму прозрачно )

    this.setState({
        cart: newCart,
    })
    
    // in render...
    <div className="cart__total">       
      Total amount - {calculateSum(this.state.cart)}
    </div>
    
0 голосов
/ 30 мая 2018

1. this.setState является асинхронным, поэтому при вызове функции sumTotalAmount состояние не обновляется к тому времени, поэтому вы будете получать сумму всех элементов, даже если вы удалили один элемент.

2. Первоначально вы не вычисляете сумму, поэтому она отображает сумму как 0. Поэтому попробуйте вызвать эту функцию sumTotalAmount в componentDidMount.

0 голосов
/ 30 мая 2018

Бруно добавил кусок кода, чтобы помочь решить проблему, начиная с:

  Total amount - {this.state.totalAmount}

До:

  Total amount - {this.state.cart.map( ( product ) => {
    return Number(product.quantity) * Number(product.price)
 }).reduce( ( total, current ) => total += current )}

Проблема имеет две стороны:

1)totalAmount инициализируется равным 0, а sumTotalAmount не вызывается для его обновления до тех пор, пока вы не внесете изменения в корзину.

2) totalAmount отстает, особенно при последнем удалении, поскольку каждый вызов sumTotalAmount полагаетсяв состоянии, которое может быть неактуальным (упомянутая асинхронная часть, упомянутая Бруно).

Я бы передал вашу (обновленную) корзину в sumTotalAmount и использовал бы ее выходные данные для установки totalAmount как при создании, так и при изменениях:

constructor( props ) {
    super( props );

    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );

    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: this.sumTotalAmount(this.props.products)
    };

}


handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
        return product.id != id;
    } );

    this.setState( {
        cart: cart,
        totalAmount: this.sumTotalAmount(cart)
    } );

    //this.sumTotalAmount(cart);
}

// Make a similar change to handleChangeQuantity


sumTotalAmount(cart) {
    //let cart = this.state.cart;
    let totalAmount = cart.map( ( product ) => {
        return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current );

    //this.setState( {
    //  totalAmount
    //} );
    return totalAmount;
}
0 голосов
/ 30 мая 2018

На самом деле проблема в том, что вы вызываете метод sum сразу после setState, а setState асинхронный, поэтому при выполнении суммы состояние не изменяется, setState получает второй параметр, обратный вызов, который будет выполнен после состояния.обновлен, см. документ ниже https://reactjs.org/docs/react-component.html#setstate

Работа с sumTotalAmount на основе prevState решает проблему

 sumTotalAmount() {   
        this.setState( (prevState, props) => { return { totalAmount : prevState.cart.map(( product ) => {
            return Number(product.quantity) * Number(product.price);
        } ).reduce( ( total, current ) => total += current )}
      })
  }

const products = [
  {
  	id: "1",
    title: "item 1",
    price: "2450",
    left: "14",
    quantity: "1"
  },
  {
  	id: "2",
    title: "item 2",
    price: "2450",
    left: "178",
    quantity: "1"
  },
  {
  	id: "3",
    title: "item 3",
    price: "2450",
    left: "1",
    quantity: "1"
  },
  {
  	id: "4",
    title: "item 4",
    price: "2450",
    left: "12",
    quantity: "1"
  }
];

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: 0
    };
    
    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );
  }
  
  
  handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
    	return product.id != id;
    } );
    
    this.setState( {
    	cart
    } );
    
    this.sumTotalAmount();
  }
  
  handleChangeQuantity( e, id ) {
    let cart = this.state.cart;
    cart = cart.map( ( product ) => {
    	if (product.id == id ) {
      	product.quantity = e.target.value;
      }
     
    	return product;
    } );
    
    this.setState( {
    	cart
    } );
    this.sumTotalAmount();
  }
  
  sumTotalAmount() {
     
    this.setState( (prevState, props) => { return { totalAmount : prevState.cart.map(( product ) => {
    	return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current )}
  })
                  }

  render() {
    return(
      <div>
        <div className="cart">
          {
            this.state.cart.map( ( item, index ) => {
              return(
                <Product key={index} item={item}
                handleRemoveProduct={this.handleRemoveProduct}
                handleChangeQuantity={this.handleChangeQuantity}
                />
              )
            })
          }
        </div>
        <div className="cart__total">    	
          Total amount - {this.state.totalAmount}
        </div>
      </div>
    )
  }
}

const Product = ( props ) => (
  <div className="cart__product">
    {props.item.title} <a href="#" onClick={() => props.handleRemoveProduct(props.item.id)}>Remove</a>
     <input type="number" name="quantity" min="1" max={props.item.left} value={props.item.quantity} onChange={(e) => props.handleChangeQuantity(e, props.item.id)}/>
  </div>
);

ReactDOM.render(<App products = {products} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
...