Javascript setState() 在 componentDidUpdate() 内部

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30528348/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:20:22  来源:igfitidea点击:

setState() inside of componentDidUpdate()

javascriptreactjsecmascript-6

提问by Katerina Pavlenko

I'm writing a script which moves dropdown below or above input depending on height of dropdown and position of the input on the screen. Also I want to set modifier to dropdown according to its direction. But using setStateinside of the componentDidUpdatecreates an infinite loop(which is obvious)

我正在编写一个脚本,它根据下拉菜单的高度和屏幕上输入的位置在输入下方或上方移动下拉菜单。此外,我想根据其方向将修饰符设置为下拉列表。但是setState在内部使用componentDidUpdate会创建一个无限循环(这很明显)

I've found a solution in using getDOMNodeand setting classname to dropdown directly, but i feel that there should be a better solution using React tools. Can anybody help me?

我已经找到了getDOMNode直接使用和设置 classname 为下拉列表的解决方案,但我觉得使用 React 工具应该有更好的解决方案。有谁能够帮助我?

Here is a part of working code with getDOMNode(i a little bit neglected positioning logic to simplify code)

这是工作代码的一部分getDOMNode(有点忽略定位逻辑以简化代码)

let SearchDropdown = React.createClass({
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        el.classList.remove('dropDown-top');
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            el.classList.add('dropDown-top');
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        return (
            <DropDown >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

and here is code with setstate (which creates an infinite loop)

这是带有 setstate 的代码(它创建了一个无限循环)

let SearchDropdown = React.createClass({
    getInitialState() {
        return {
            top: false
        };
    },
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        if (this.state.top) {
           this.setState({top: false});
        }
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            if (!this.state.top) {
              this.setState({top: true});
           }
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        let class = cx({'dropDown-top' : this.state.top});
        return (
            <DropDown className={class} >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

采纳答案by damianmr

You can use setStateinside componentDidUpdate. The problem is that somehow you are creating an infinite loop because there's no break condition.

你可以setState在里面使用componentDidUpdate。问题在于,您以某种方式创建了一个无限循环,因为没有中断条件。

Based on the fact that you need values that are provided by the browser once the component is rendered, I think your approach about using componentDidUpdateis correct, it just needs better handling of the condition that triggers the setState.

基于在组件呈现后您需要浏览器提供的值这一事实,我认为您使用的方法componentDidUpdate是正确的,它只需要更好地处理触发setState.

回答by Abdennour TOUMI

The componentDidUpdatesignature is void::componentDidUpdate(previousProps, previousState). With this you will be able to test which props/state are dirty and call setStateaccordingly.

componentDidUpdate签名void::componentDidUpdate(previousProps, previousState)。有了这个,您将能够测试哪些道具/状态是脏的并相应地调用setState

Example:

例子:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}

回答by mickeymoon

If you use setStateinside componentDidUpdateit updates the component, resulting in a call to componentDidUpdatewhich subsequently calls setStateagain resulting in the infinite loop. You should conditionally call setStateand ensure that the condition violating the call occurs eventually e.g:

如果您setStatecomponentDidUpdate它内部使用它更新组件,则会导致调用componentDidUpdate,随后setState再次调用导致无限循环。您应该有条件地调用setState并确保最终发生违反调用的条件,例如:

componentDidUpdate: function() {
    if (condition) {
        this.setState({..})
    } else {
        //do something else
    }
}

In case you are only updating the component by sending props to it(it is not being updated by setState, except for the case inside componentDidUpdate), you can call setStateinside componentWillReceivePropsinstead of componentDidUpdate.

如果您只是通过向组件发送 props 来更新组件(它不会被 setState 更新,除了 componentDidUpdate 内部的情况),您可以调用setStateinsidecomponentWillReceiveProps而不是componentDidUpdate.

回答by Nikhil Mahirrao

This example will help you to understand the React Life Cycle Hooks.

这个例子将帮助你理解React Life Cycle Hooks

You can setStatein getDerivedStateFromPropsmethod i.e. staticand trigger the method after props change in componentDidUpdate.

您可以setStategetDerivedStateFromProps方法 ie 中static并在道具更改后触发方法componentDidUpdate

In componentDidUpdateyou will get 3rdparam which returns from getSnapshotBeforeUpdate.

componentDidUpdate您将获得3PARAM从返回getSnapshotBeforeUpdate

You can check this codesandbox link

您可以检查此代码和框链接

// Child component
class Child extends React.Component {
  // First thing called when component loaded
  constructor(props) {
    console.log("constructor");
    super(props);
    this.state = {
      value: this.props.value,
      color: "green"
    };
  }

  // static method
  // dont have access of 'this'
  // return object will update the state
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
    return {
      value: props.value,
      color: props.value % 2 === 0 ? "green" : "red"
    };
  }

  // skip render if return false
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    // return nextState.color !== this.state.color;
    return true;
  }

  // In between before real DOM updates (pre-commit)
  // has access of 'this'
  // return object will be captured in componentDidUpdate
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return { oldValue: prevState.value };
  }

  // Calls after component updated
  // has access of previous state and props with snapshot
  // Can call methods here
  // setState inside this will cause infinite loop
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
  }

  static getDerivedStateFromError(error) {
    console.log("getDerivedStateFromError");
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    console.log("componentDidCatch: ", error, info);
  }

  // After component mount
  // Good place to start AJAX call and initial state
  componentDidMount() {
    console.log("componentDidMount");
    this.makeAjaxCall();
  }

  makeAjaxCall() {
    console.log("makeAjaxCall");
  }

  onClick() {
    console.log("state: ", this.state);
  }

  render() {
    return (
      <div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
        <p style={{ color: this.state.color }}>Color: {this.state.color}</p>
        <button onClick={() => this.onClick()}>{this.props.value}</button>
      </div>
    );
  }
}

// Parent component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 1 };

    this.tick = () => {
      this.setState({
        date: new Date(),
        value: this.state.value + 1
      });
    };
  }

  componentDidMount() {
    setTimeout(this.tick, 2000);
  }

  render() {
    return (
      <div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
        <p>Parent</p>
        <Child value={this.state.value} />
      </div>
    );
  }
}

function App() {
  return (
    <React.Fragment>
      <Parent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

回答by gradosevic

I would say that you need to check if the state already has the same value you are trying to set. If it's the same, there is no point to set state again for the same value.

我会说您需要检查状态是否已经具有您尝试设置的相同值。如果相同,则没有必要为相同的值再次设置状态。

Make sure to set your state like this:

确保像这样设置你的状态:

let top = newValue /*true or false*/
if(top !== this.state.top){
    this.setState({top});
}

回答by Sanjay

I had a similar problem where i have to center the toolTip. React setState in componentDidUpdate did put me in infinite loop, i tried condition it worked. But i found using in ref callback gave me simpler and clean solution, if you use inline function for ref callback you will face the null problem for every component update. So use function reference in ref callback and set the state there, which will initiate the re-render

我有一个类似的问题,我必须将工具提示居中。在 componentDidUpdate 中反应 setState 确实让我陷入无限循环,我尝试了它工作的条件。但是我发现在 ref 回调中使用给了我更简单和干净的解决方案,如果您使用内联函数进行 ref 回调,您将面临每个组件更新的空问题。所以在 ref 回调中使用函数引用并在那里设置状态,这将启动重新渲染