调用setState后,ReactJS元素不会更新

时间:2018-04-10 14:13:35

标签: javascript reactjs state render jsx

下午好, 我有一个React组件,它响应API调用动态呈现。我已将其中一个元素的值设置为组件中的状态。在onClick函数(minusOne)期间,该值应该更改。 该值最初基于状态成功呈现,该函数确实改变了状态,但是尽管状态改变,但渲染的元素保持不变。有没有人知道为什么会这样? 如果您有任何疑问,请随时联系!

export class Cart extends React.Component {
constructor(props) {
    super(props);
    this.state={
        quantities: []
    };
    this.minusOne = this.minusOne.bind(this);
}

minusOne(i) {
    var self = this;
    return function() {
        let quantities = self.state.quantities;
        if (quantities[i] > 1) {
            quantities[i] --;
        }
        self.setState({
            quantities
        })
    }
}
componentDidMount() {
    let cart = this.props.cartTotals;
    this.setState({
        cart
    });
    if(cart.lines) {
        let cartTotal = [];
        let quantities = [];
        for (var i = 0; i < cart.lines.length; i++) {
            if(cart.lines[i]) {
                quantities.push(cart.lines[i].quantity);
            }
        }

        //Initial setting of state
        this.setState({
            quantities
        })

        Promise.all(
            cart.lines.map(
                (cart, i) => axios.get('http://removed.net/article/' + cart.sku)
            )
        ).then(res => {
            const allCartItems = res.map((res, i) => {
                const data = res.data;
                return(
                    <div key={i} className="cart-item-container">
                        <img className ="cart-item-picture" src={data.image} name={data.name} />
                        <div className="cart-item-description">
                            <p>{data.name}</p>
                            <p>{data.price.amount} {data.price.currency}</p>
                        </div>
                        <div className="cart-item-quantity">
                            <button onClick={this.minusOne(i)} name="minus">-</button>



                            //This is the troublesome element
                            <p className="cart-current-quantity">{this.state.quantities[i]}</p>



                            <button name="plus">+</button>
                        </div>
                    </div>
                )
            })
            this.setState({
                allCartItems
            })
        })
    }
}
render() {
    return (
                    {this.state.allCartItems}
    );
  }
}

感谢阅读!任何建议都会有所帮助。

3 个答案:

答案 0 :(得分:0)

有两个问题:

  1. 首先,您需要在render()中渲染(包括onClick所在的位置)。 ConponentDidMount只被调用一次,并且应该执行初始化但不能渲染。
  2. 然后,minusOne中出现问题:
    quantities指向this.state.quantities。因此,您正在更改旧状态,React查看旧状态和新状态,看到没有更改,并且dodes没有渲染,尽管值已更改。
  3. 如果要将this.state.quantities复制到新数组,例如:

    newQ = this.state.quantities.slice(0, -1);
    

    然后修改newQ,然后执行

    this.setState({ quantities: newQ  });
    

    它应该有用。

答案 1 :(得分:0)

---编辑--- 我删除了之前写的所有内容,以使其更简洁。

您的问题是您要将要渲染的内容分配给componentDidMount中的变量。此函数只调用一次,因此您只需将变量allCartItems赋值一次。 setState函数没有任何效果,因为它不会触发componentDidMount,因此您的变量allCartItems不会被重新分配。

你能做什么?那么你可以做很多事情来增强你的代码。首先,我会告诉您如何解决问题,然后再给您一些改进

要解决在调用setState时组件未更新的问题,应将jsx移动到渲染方法。在componentDidMount中,您只需获取渲染组件所需的所有数据,一旦拥有它,您就可以设置一个标志,例如ready to true。您可以在下面看到代码看起来如何的示例。

import React from 'react';

class Cart extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      carts: null,
      ready: false,
    };
  }

  componentDidMount() {
    fetch('www.google.com').then((carts) => {
      this.setState({
        carts,
        ready: true,
      });
    });
  }

  render() {
    const myCarts = <h2> Count {this.state.carts} </h2>;

    return (
      {
        this.state.ready
        ? myCarts
        : <h2> Loading... </h2>
      }
    );
  }
}

我为您制作了一个带有简单计数器的演示,其中包含对您的案例的一些解释以及如何使其工作。你可以查看codesandbox。在NotWorkingCounter中,您可以看到与未更新的变量组件中的问题相同的问题。在WorkingCount中,您可以看到一个示例,其中我实现了上面写的内容,等待数据到达,然后再渲染它。

有关代码的更多建议:

下面的这两个语法是相同的。一个更简洁。

class Cart extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      carts: null,
      ready: false,
    };
  }
}

class Cart extends React.Component {
    state = {
      carts: null,
      ready: false,
    }
}

如果你想绑定你的上下文,我建议使用arrow函数。下面你可以看到你的例子简化了,以及如何用较少的语法实现同样的事情的例子。

export class Cart extends React.Component {
  constructor(props) {
      super(props);
      this.minusOne = this.minusOne.bind(this);
  }

  minusOne(i) {
    ///
  }
}

export class Cart extends React.Component {
  constructor(props) {
      super(props);
  }

  minusOne = (i) => {
    /// minus one function
  }
}

如果使用箭头功能并且要小得多,那么你的minusOne也可以重写,

  minusOne = (i) => (i) => {
    let quant = self.state.quantities[i];
    if(quant > 1) {
      this.setState({
        quantities: quant-1,
      })
    }
  }

componentDidMount中,您拨打this.setState两次。每次调用此函数时,您的组件都会被重新渲染。所以在你的组件中发生的事情是当你的组件第一次被渲染时,一旦它被挂起componentDidMount被调用,你再次调用this.setState两次。这意味着在用户看到您的组件之前,您的组件将在最佳情况下呈现三次。如果你得到多个承诺,这意味着你会更多地回报你的状态。这可以为您的组件创造大量负载以应对。如果您将每个组件重新渲染三次或更多次,则一旦应用程序增长,您最终会遇到一些性能问题。尽量不要多次调用componentDidUpdate中的setState。 在您的情况下,您第一次拨打setState是完全没必要的,只是创建负载。您仍然可以访问承诺中的数量。只需在promise.then()结尾处使用两个元素调用setState。

在下面的示例中,您使用索引i作为键。这不是一个好的案例练习,反应也应该在控制台中至少记录一个警告。您需要使用不是索引的唯一标识符。如果您使用索引,您可以获得难以首次亮相的副作用和奇怪的渲染。阅读更多信息here

then(res => {
              const allCartItems = res.map((res, i) => {
                  const data = res.data;
                  return(
                      <div key={i} className="cart-item-container">

另一个建议是将所有var替换为constlet,因为var会将您的变量公开到全局范围。如果您不明白这意味着什么,请阅读this

最后但并非最不重要的是看看object deconstruction。它可以帮助您清理代码并使其更好地抵御不必要的副作用。

答案 2 :(得分:0)

我认为你不需要在minusOne(i)方法中返回一个函数。只是更新状态就足够了。您应该按特定ID更改数组。        let quantities = self.state.quantities; let mutatedQuantities = quantities.map((el, index) => { return (index === i) ? el - 1 : el; }) this.setState({quantities: [...mutatedQuantities]})