【问题标题】:setState() inside of componentDidUpdate()setState() 在 componentDidUpdate() 内
【发布时间】:2015-08-12 05:56:23
【问题描述】:

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

我找到了使用getDOMNode 并直接将类名设置为下拉菜单的解决方案,但我觉得应该有更好的解决方案使用 React 工具。有人可以帮帮我吗?

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

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>
        );
    }
});

这里是带有 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>
        );
    }
});

【问题讨论】:

  • 我认为这里的诀窍是setState总是触发重新渲染。而不是检查state.top 并多次调用setState,只需跟踪您希望state.top 在局部变量中的内容,然后在componentDidUpdate 结束时调用setState,前提是您的局部变量没有匹配state.top。就目前而言,您在第一次重新渲染后立即重置 state.top,这会使您陷入无限循环。
  • this fiddle 中查看componentDidUpdate 的两种不同实现。
  • 该死的!局部变量解决了整个问题,我怎么没被 mysef 弄明白!谢谢!
  • 我认为你应该接受下面的答案。如果你再读一遍,我想你会发现它确实充分回答了最初的问题。
  • 为什么没有人建议将条件转移到componentShouldUpdate

标签: javascript reactjs ecmascript-6


【解决方案1】:

this.setState 在循环中没有中断条件时在 ComponentDidUpdate 中使用时会创建一个无限循环。 您可以使用redux在if语句中设置变量为true,然后在条件中设置变量为false,然后它将起作用。

类似的东西。

if(this.props.route.params.resetFields){

        this.props.route.params.resetFields = false;
        this.setState({broadcastMembersCount: 0,isLinkAttached: false,attachedAffiliatedLink:false,affilatedText: 'add your affiliate link'});
        this.resetSelectedContactAndGroups();
        this.hideNext = false;
        this.initialValue_1 = 140;
        this.initialValue_2 = 140;
        this.height = 20
    }

【讨论】:

    【解决方案2】:

    您可以在 componentDidUpdate 中使用 setState

    【讨论】:

      【解决方案3】:

      本示例将帮助您了解 React 生命周期挂钩

      您可以在getDerivedStateFromProps方法中setState,即static,并在componentDidUpdate中的props更改后触发该方法。

      componentDidUpdate 中,您将获得从getSnapshotBeforeUpdate 返回的3rd 参数。

      你可以查看这个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>

      【讨论】:

        【解决方案4】:

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

        示例:

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

        【讨论】:

        • componentDidMount 没有任何参数,仅在创建组件时调用,因此不能用于所述目的。
        • @Jules 谢谢!我曾经写过 componentDidMount ,所以当我写答案时,著名的名字级联了?再次,谢谢&很好的赶上!
        • componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
        • 我知道你关心@AshokR。您减少了 arg 名称。但是“prev”可能意味着防止不是以前的.. hhh 。 .kidding :)
        【解决方案5】:

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

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

        如果你只是通过发送 props 来更新组件(它不是由 setState 更新的,除了 componentDidUpdate 内部的情况),你可以在 componentWillReceiveProps 内部调用 setState 而不是 componentDidUpdate

        【讨论】:

        • 老问题,但 componentWillReceiveProps 已弃用,应使用 componentWillRecieveProps。您不能在此方法中设置状态。
        • 你的意思是getDerivedStateFromProps
        【解决方案6】:

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

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

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

            • “中断条件”是什么意思?检查状态是否已经设置而不重置?
            • 我同意这一点,我唯一的补充意见是 componentDidUpdate 中可能不需要添加/删除类,而可以根据需要在 render 中添加。
            • 但是类的添加/删除取决于在componentDidUpdate中签入的下拉位置,您建议检查两次吗?据我了解,componentDidUpdate 在 render() 之后调用,因此在 render() 中添加/删除类是没有用的
            • 我已经用 setstate 添加了我的代码,你能检查一下并指出我的错误吗?或者给我看一些不会导致循环的例子
            • componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //做点什么 } }
            猜你喜欢
            • 2016-10-22
            • 1970-01-01
            • 2019-04-24
            • 2020-02-15
            • 2019-04-25
            • 2023-04-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多