警告:setState(...):在现有状态转换期间无法更新

时间:2017-07-16 00:04:47

标签: javascript reactjs

我正在开发一个简单的待办事项清单' react app(React.js的新手)。我已将项目添加到列表中,但删除项目会引发一个问题。在我的父反应组件中,我有以下代码:

Displays info
Makes empty list studentsNames
Repeats the following 10 times:
 Asks the user for ten names and put the string in a variable
 Asks the user for the ten ages and tries to turn a string to an integer* and stores that in a variable
 Each loop, the variables are overwritten
Gets the highest value from the age variable which would be the last and only one, saves it to a variable
Display that variable

我的print("your info") names = [] ages = [] for __ in range(10): name.append(input("Enter student name: ")) age.append(int(input("Age: "))) oldest_age = max(ages) index = ages.index[oldest_age] team_leader = names[index] print(team_leader) 组件:

import ToDoEntries from './to_do_entries.jsx';

class ToDoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { list: [] }
    this.add = this.addItem.bind(this);
    this.removeItem = this.removeItem.bind(this);
  }

  addItem(e) { //removed to avoid tl:dr }

  render() {
    return(
      <form onSubmit={this.add}>
        <input placeholder='Enter item' type='text' ref={(el) => {this._input = el;} }/>
        <button>Add</button>
      </form>

      <ToDoEntries entries={this.state.list}
        removeCallback={this.removeItem}
      />
    );
  }

}

运行此代码带来:

  

警告:setState(...):在现有状态期间无法更新   过渡

问题:

为什么to_do_entries.jsx的渲染会在添加项目时立即执行回调,即:

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

  renderItems() {
    const { entries, removeCallback } = this.props;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = entries.map(function(item) {
      return(<li onClick={removeCallback} key={item.key}>{item.text}</li>)
    })

    return listItems;
  }

  render() {
    var todoEntries = this.renderItems();

    return(
      <ul>
        {todoEntries}
      </ul>
    );
  }
}

export default ToDoEntries;

然而,将to_do_entries.jsx添加到removeCallback即。 var listItems = entries.map(function(item) { return(<li onClick={removeCallback(id)} key={item.key}>{item.text}</li>) }) 没有?

3 个答案:

答案 0 :(得分:4)

问题出在这一部分:

onClick={removeCallback(id)}

我们需要将函数传递给onClick,而不是值。当我们将()与functionName一起使用时,这意味着您正在调用该方法并将其结果分配给onClick,如果您执行setState,则会创建无限循环在removeCallback中,因为这个循环:

render ->  removeCallback()  ->  setState ->
  ^                                         |
  |                                         |
  |                                         |
   -----------------------------------------

这就是你收到错误的原因。

检查代码段,了解abc and abc()

之间的差异

&#13;
&#13;
function abc(){
   return 'Hello';
}

console.log('without () = ', abc);     //will return the function
 
console.log('with () = ', abc());      //will return the function result (value)
&#13;
&#13;
&#13;

  

为何使用onClick={removeCallback.bind(null, id)}

因为bind会创建一个新函数,并将该函数指定给click事件,所以当你不自动点击任何项目时,{@ 1}}会被调用。

根据 MDN Doc

  

bind()函数创建一个新的绑定函数(BF)。 BF是一个   外来的功能对象(来自ECMAScript 2015的术语)包装了   原始功能对象。调用BF通常会导致   执行包装函数。

检查 React DOC: Handling events in JSX

检查此答案以获取有关绑定的更多详细信息: Use of the JavaScript 'bind' method

答案 1 :(得分:2)

我会反对这一点,并对我为您编写的示例使用类似的方法。渲染绑定到状态的待办事项列表,然后将相关信息传递回父组件以删除该项目。在这种情况下,我使用todo的索引来拼接数组,以便将其删除。

当呈现每个todo <li>时,会立即调用当前的onClick,因为它只是一个导致问题的函数调用。 .bind解决了这个问题,因为它会在你单击元素时创建一个新函数,这就是函数不立即调用的原因。

但是,这通常被认为是不好的做法,因为每次组件都会一次又一次地创建这个功能。通过屏幕上的todo数量将其倍增,您将失去性能。这是一个小问题,但我的例子展示了如何解决这个问题。 https://codepen.io/w7sang/pen/VWNLJp

// App
class App extends React.Component{
  constructor(props) {
    super(props);
    this.state = { list: [] }
    this.add = this.addItem.bind(this);
    this.removeItem = this.removeItem.bind(this);
  }
  addItem(e) { 
    e.preventDefault();
    this.setState({
      list: [ 
        ...this.state.list, 
        {
          key: Math.random(1,10000),
          text: this._input.value
        }
      ]
    })
  }
  removeItem(payload){
    this.setState({
      list: [ 
        ...this.state.list.slice(0, payload.index),
        ...this.state.list.slice(payload.index + 1)
      ]
    })
  }
  render() {
    return(
      <div>
        <form onSubmit={this.add}>
          <input placeholder='Enter item' type='text' ref={(el) => {this._input = el;} }/>
          <button>Add</button>
        </form>
        <ToDoEntries entries={this.state.list} removeItem={this.removeItem} />
      </div>
    );
  }
}

// TodoEntries [STATELESS]
const ToDoEntries = ({ entries, removeItem } ) => {
  return(
    <ul>
      { entries.map((item, index) => {
        return(<Todo key={item.key} index={index} item={item} removeItem={removeItem} />)
      }) }
    </ul>
  );
}

// Todo
class Todo extends React.Component {
  constructor(props){
    super(props);
    this.state = {};
    this.remove = this.remove.bind(this);
  }
  remove() {
    const { index, removeItem } = this.props;
    removeItem({
      index
    });
  }
  render() {
    return <li onClick={this.remove}>{this.props.item.text}</li>
  }
}

ReactDOM.render(<App />,document.getElementById('app'));
<div id="app"></div>

答案 2 :(得分:2)

  

为什么to_do_entries.jsx的渲染会立即执行回调?

好吧,当您对todo每个<li/>列表的映射调用removeCallback函数而不是将其分配给onClick时。

当前代码

<li onClick={removeCallback(id)} </li>

相当于:

var result = removeCallback(id);
<li onClick={result} </li>

您已正确指出使用bind会有效。这是由于这种行为使得它在这些情况下非常有用。

See the mdn docs for more info,但我会在这里引用重要部分:

  

bind ...创建并返回一个新功能,在调用时...

在你的情况下,当使用bind,并将它赋给onClick时,你正在创建一个新函数,当实际触发click事件时将调用该函数,而不是在渲染元素时。

查看removeCallback.bind(null, id)的另一种方式是这样的:

var newFunc = () => {
  return removeCallback(id);
}
<li onClick={newFunc} </li>