componentDidUpdate()内のsetState()


131

ドロップダウンの高さと画面上の入力の位置に応じて、ドロップダウンを入力の下または上に移動するスクリプトを書いています。また、修飾子をその方向に応じてドロップダウンに設定したいと思います。しかし、setState内部で使用componentDidUpdateすると無限ループが発生します(これは明らかです)

getDOMNodeclassnameを直接使用してドロップダウンに設定することで解決策を見つけましたが、Reactツールを使用するより良い解決策があるはずです。誰かが私を助けてくれますか?

以下は、機能するコードの一部です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>
        );
    }
});

そしてこれが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>
        );
    }
});

9
ここでのトリックは、それsetState常に再レンダリングをトリガーすることだと思います。複数回チェックstate.topして呼び出すのではなく、ローカル変数に含めsetStateたいものを追跡し、ローカル変数が一致しない場合にのみstate.topcomponentDidUpdate呼び出しの最後に一度だけ追跡します。現在の状態では、最初の再レンダリングの直後にリセットされ、無限ループに入ります。setStatestate.topstate.top
Randy Morris、

2
二つの異なる実装を参照してくださいcomponentDidUpdateこのフィドル
ランディモリス

畜生!ローカル変数は問題全体を解決しますが、mysefによってそれを理解できなかったのです!ありがとうございました!
Katerina Pavlenko、2015年

1
以下の答えを受け入れるべきだと思います。もう一度読んでみれば、最初の質問に十分答えることができると思います。
ランディモリス

なぜ誰もが状態をに移行することを提案していないのcomponentShouldUpdateですか?
Patrick Roberts

回答:


116

setState内部で使用できますcomponentDidUpdate。問題は、ブレーク条件がないため、どういうわけか無限ループを作成していることです。

コンポーネントがレンダリングされたときにブラウザーから提供される値が必要であるという事実に基づいて、私は使用に関するあなたのアプローチcomponentDidUpdateは正しいと思いますsetState


4
「ブレークコンディション」とはどういう意味ですか?状態がすでに設定されているかどうかを確認し、それをリセットしていませんか?
Katerina Pavlenko

私はこれに同意します。私の唯一の追加コメントは、クラスの追加/削除はおそらく不要でcomponentDidUpdateあり、render代わりに必要に応じて追加できるということです。
ランディモリス

しかし、クラスの追加/削除は、componentDidUpdateでチェックインされるドロップダウン位置に依存します。これを2回チェックすることをお勧めしますか?そして、私が理解しているように、componentDidUpdateはAFTER render()と呼ばれるため、render()でクラスを追加/削除するのは無意味です
Katerina Pavlenko

setstateを使用してコードを追加しました。チェックして、間違いを指摘できますか?または、ループを引き起こさない例をいくつか見せてください
Katerina Pavlenko '29

2
componentDidUpdate(prevProps、prevState){if(prevState.x!== this.state.x){//何かを行う}}
Ashok R

68

componentDidUpdate署名がありますvoid::componentDidUpdate(previousProps, previousState)。これにより、どの小道具/状態がダーティであるかをテストし、それにsetState応じて呼び出すことができます。

例:

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

componentDidMount引数はなく、コンポーネントが作成されたときにのみ呼び出されるため、説明されている目的には使用できません。
Jules

@Julesありがとう!以前はを書いていたcomponentDidMountので、答えを書いたとき、有名な名前がカスケードされました😮もう一度、ありがとう&素晴らしいキャッチアップ!
Abdennour TOUMI 2017

componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ashok R

私はあなたの懸念@AshokRを知っています。引数名を減らします。しかし、「前」は前ではないことを意味します。.kidding :)
Abdennour TOUMI

58

setState内部componentDidUpdateで使用すると、コンポーネントが更新され、呼び出しcomponentDidUpdateがその後setState再び呼び出され、無限ループが発生します。条件付きで呼び出しsetState、呼び出しに違反する状態が最終的に発生することを確認する必要があります。例:

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

小道具をコンポーネントに送信することによってのみコンポーネントを更新している場合(componentDidUpdate内の場合を除いて、setStateによってコンポーネントは更新されていません)、の代わりにsetState内部を呼び出すことができます。componentWillReceivePropscomponentDidUpdate


2
古い質問ですが、componentWillReceivePropsは非推奨であり、componentWillRecievePropsを使用する必要があります。このメソッド内でsetStateを使用することはできません。
Brooks DuBois 2018年

つまりgetDerivedStateFromProps
adi518

5

この例は、React Life Cycle Hooksを理解するのに役立ちます。

次のことが可能setStategetDerivedStateFromPropsすなわち方法staticや小道具は、変更後の方法を誘発しますcomponentDidUpdate

では、から戻る3番目のパラメータをcomponentDidUpdate取得します。getSnapshotBeforeUpdate

あなたはこのcodesandboxリンクをチェックすることができます

// 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>


2

設定しようとしているのと同じ値がすでに状態にあるかどうかを確認する必要があると思います。同じ場合は、同じ値に再度状態を設定しても意味がありません。

必ず次のように状態を設定してください:

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

-1

toolTipを中央に配置する必要があるのと同じような問題がありました。componentDidUpdateのReact setStateは私を無限ループに入れました、私はそれが機能する状態を試しました。しかし、refコールバックで使用すると、よりシンプルでクリーンなソリューションが得られることがわかりました。refコールバックにインライン関数を使用すると、すべてのコンポーネントの更新でnullの問題が発生します。したがって、refコールバックで関数参照を使用し、そこに状態を設定すると、再レンダリングが開始されます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.