setState () innerhalb von componentDidUpdate ()

130

Ich schreibe ein Skript, das die Dropdown-Liste je nach Dropdown-Höhe und Position der Eingabe auf dem Bildschirm unter oder über die Eingabe verschiebt. Außerdem möchte ich den Modifikator entsprechend seiner Richtung auf Dropdown setzen. Wenn Sie jedoch das setStateInnere des verwenden, componentDidUpdateentsteht eine Endlosschleife (was offensichtlich ist).

Ich habe eine Lösung gefunden, getDOMNodeindem ich den Klassennamen direkt als Dropdown-Liste verwendet und festgelegt habe, aber ich bin der Meinung, dass es mit den React-Tools eine bessere Lösung geben sollte. Kann mir jemand helfen?

Hier ist ein Teil des Arbeitscodes mit getDOMNode(ua etwas vernachlässigte Positionierungslogik zur Vereinfachung des Codes)

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

und hier ist Code mit setstate (der eine Endlosschleife erzeugt)

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>
        );
    }
});
Katerina Pavlenko
quelle
9
Ich denke , hier ist der Trick also setStatewird immer ausgelöst eine erneute machen. Anstatt mehrmals zu prüfen state.topund aufzurufen setState, verfolgen Sie einfach state.top, was Sie in einer lokalen Variablen sein möchten , und dann nur einmal am Ende des componentDidUpdateAufrufs setState, wenn Ihre lokale Variable nicht übereinstimmt state.top. So wie es jetzt ist, werden Sie sofort state.topnach dem ersten erneuten Rendern zurückgesetzt, wodurch Sie in die Endlosschleife geraten.
Randy Morris
2
Sehen Sie sich die zwei verschiedenen Implementierungen componentDidUpdatein dieser Geige an .
Randy Morris
verdammt! lokale Variable löst das ganze Problem, wie hätte ich es nicht von mysef herausgefunden! Danke dir!
Katerina Pavlenko
1
Ich denke, Sie sollten die Antwort unten akzeptieren. Wenn Sie es noch einmal lesen, werden Sie feststellen, dass es die ursprüngliche Frage ausreichend beantwortet.
Randy Morris
Warum hat niemand vorgeschlagen, den Zustand zu verschieben componentShouldUpdate?
Patrick Roberts

Antworten:

115

Sie können setStateinnen verwenden componentDidUpdate. Das Problem ist, dass Sie irgendwie eine Endlosschleife erstellen, weil es keine Unterbrechungsbedingung gibt.

Aufgrund der Tatsache, dass Sie Werte benötigen, die vom Browser nach dem Rendern der Komponente bereitgestellt werden, denke ich, dass Ihr Ansatz zur Verwendung componentDidUpdatekorrekt ist. Sie müssen lediglich besser mit der Bedingung umgehen, die die auslöst setState.

damianmr
quelle
4
Was meinst du mit "Pausenbedingung"? Überprüfen Sie, ob der Status bereits festgelegt ist, und setzen Sie ihn nicht zurück.
Katerina Pavlenko
Ich stimme dem zu, mein einziger zusätzlicher Kommentar wäre, dass das Hinzufügen / Entfernen von Klassen wahrscheinlich unnötig ist componentDidUpdateund renderstattdessen einfach nach Bedarf hinzugefügt werden kann .
Randy Morris
Das Hinzufügen / Entfernen von Klassen hängt jedoch von der Dropdown-Position ab, die in componentDidUpdate eingecheckt ist. Sie schlagen vor, sie zweimal zu überprüfen. Und wie ich verstehe, heißt componentDidUpdate AFTER render (), daher ist es nutzlos, eine Klasse in render () hinzuzufügen / zu entfernen
Katerina Pavlenko
Ich habe meinen Code mit setstate hinzugefügt. Können Sie ihn überprüfen und auf meinen Fehler hinweisen? oder zeigen Sie mir ein Beispiel, das keine Schleife verursachen würde
Katerina Pavlenko
1
componentDidUpdate (prevProps, prevState) {if (prevState.x! == this.state.x) {// Mach etwas}}
Ashok R
68

Die componentDidUpdateUnterschrift ist void::componentDidUpdate(previousProps, previousState). Damit können Sie testen, welche Requisiten / Zustände verschmutzt sind und entsprechend anrufen setState.

Beispiel:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}
Abdennour TOUMI
quelle
componentDidMounthat keine Argumente und wird nur aufgerufen, wenn die Komponente erstellt wird, kann also nicht für den beschriebenen Zweck verwendet werden.
Jules
@ Jules Danke! Früher habe ich geschrieben componentDidMount, und als ich die Antwort schrieb, kaskadierte der berühmte Name.
Abdennour TOUMI
componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ashok R
Ich kenne Ihr Anliegen @AshokR. Sie reduzieren den Arg-Namen. aber "prev" kann bedeuten, nicht vorher zu verhindern .. hhh. .kidding :)
Abdennour TOUMI
58

Wenn Sie setStateinnerhalb componentDidUpdatesie die Komponente aktualisiert, was zu einem Aufruf an componentDidUpdatedie anschließend ruft setStatewieder in der Endlosschleife hängen bleibt . Sie sollten bedingt anrufen setStateund sicherstellen, dass die Bedingung, die den Anruf verletzt, irgendwann auftritt, z.

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

Für den Fall , aktualisieren Sie nur die Komponente von Requisiten , um sie zu senden (es durch setState keine Aktualisierungen erfolgen, außer für den Fall innerhalb componentDidUpdate), können Sie rufen im setStateInneren componentWillReceivePropsstatt componentDidUpdate.

Mickeymoon
quelle
2
alte Frage, aber componentWillReceiveProps ist veraltet und componentWillRecieveProps sollte verwendet werden. Sie können State nicht innerhalb dieser Methode setzen.
Brooks DuBois
Du meinst getDerivedStateFromProps.
adi518
5

Dieses Beispiel hilft Ihnen, die React Life Cycle Hooks zu verstehen .

Sie können setStatein getDerivedStateFromPropsVerfahren also staticund lösen das Verfahren nach Requisiten in ändern componentDidUpdate.

In erhalten componentDidUpdateSie den 3. Parameter, der von zurückkehrt getSnapshotBeforeUpdate.

Sie können diesen Codesandbox-Link überprüfen

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

Nikhil Mahirrao
quelle
2

Ich würde sagen, dass Sie überprüfen müssen, ob der Status bereits denselben Wert hat, den Sie festlegen möchten. Wenn es dasselbe ist, macht es keinen Sinn, den Status für denselben Wert erneut festzulegen.

Stellen Sie sicher, dass Sie Ihren Status wie folgt einstellen:

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

Ich hatte ein ähnliches Problem, bei dem ich den ToolTip zentrieren muss. React setState in componentDidUpdate hat mich in eine Endlosschleife gebracht, ich habe versucht, Bedingung, dass es funktioniert. Aber ich fand, dass die Verwendung in Ref Callback mir eine einfachere und sauberere Lösung gab. Wenn Sie die Inline-Funktion für Ref Callback verwenden, werden Sie bei jedem Komponenten-Update mit dem Null-Problem konfrontiert. Verwenden Sie also die Funktionsreferenz im Ref-Rückruf und legen Sie dort den Status fest, der das erneute Rendern initiiert

Sanjay
quelle