useState Hook Setter überschreibt den Status falsch


31

Hier ist das Problem: Ich versuche, 2 Funktionen auf Knopfdruck aufzurufen. Beide Funktionen aktualisieren den Status (ich verwende den useState-Hook). Die erste Funktion aktualisiert den Wert1 korrekt auf 'neue 1', aber nach 1s (setTimeout) wird die zweite Funktion ausgelöst und der Wert 2 in 'neue 2' geändert, ABER! Der Wert1 wird auf '1' zurückgesetzt. Warum passiert dies? Danke im Voraus!

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState({ ...state, value1: "new 1" });
  };
  const changeValue2 = () => {
    setState({ ...state, value2: "new 2" });
  };

  return (
    <>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </>
  );
};

export default Test;

Könnten Sie den Status zu Beginn von protokollieren changeValue2?
DanStarns

1
Ich empfehle dringend, dass Sie das Objekt entweder in zwei separate Aufrufe aufteilen useStateoder stattdessen verwenden useReducer.
Jared Smith

Ja - zweitens das. Verwenden Sie einfach zwei Aufrufe, um State () zu verwenden
Esben Skov Pedersen

const [state, ...], und dann im Setter darauf verweisen ... Es wird immer den gleichen Zustand verwenden.
Johannes Kuhn

Beste Vorgehensweise: Verwenden Sie zwei separate useStateAufrufe, einen für jede "Variable".
Dima Tisnek

Antworten:


30

Willkommen in der Schließungshölle . Dieses Problem tritt auf, weil , wenn setStateaufgerufen, stateeine neue Speicher - Referenz bekommt, aber die Funktionen changeValue1und changeValue2aufgrund der Schließung, halten die alte anfängliche stateReferenz.

Eine Lösung, um sicherzustellen, dass der aktuelle Status setStatevon changeValue1und changeValue2abgerufen wird, besteht in der Verwendung eines Rückrufs (mit dem vorherigen Status als Parameter):

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
  };

  // ...
};

Eine breitere Diskussion zu diesem Abschlussproblem finden Sie hier und hier .


Ein Rückruf mit useState-Hook scheint eine undokumentierte Funktion zu sein. Sind Sie sicher, dass dies funktioniert?
HMR

@HMR Ja, es funktioniert und es ist auf einer anderen Seite dokumentiert. Schauen Sie sich hier den Abschnitt "Funktionsaktualisierungen" an: reactjs.org/docs/hooks-reference.html ("Wenn der neue Status mit dem vorherigen Status berechnet wird, können Sie eine Funktion an setState übergeben")
Alberto Trindade Tavares

1
@AlbertoTrindadeTavares Ja, ich habe mir auch die Dokumente angesehen und konnte nichts finden. Vielen Dank für die Antwort!
Bartek

1
Ihre erste Lösung ist nicht nur eine "einfache Lösung", sondern die richtige Methode. Die zweite würde nur funktionieren, wenn die Komponente als Singleton konzipiert ist, und selbst dann bin ich mir nicht sicher, da der Status jedes Mal ein neues Objekt wird.
Scimonster

1
Vielen Dank, dass Sie @AlbertoTrindadeTavares! Schön
José Salgado

19

Ihre Funktionen sollten folgendermaßen aussehen:

const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
};

So stellen Sie sicher, dass im aktuellen Status keine vorhandene Eigenschaft fehlt, indem Sie den vorherigen Status verwenden, wenn die Aktion ausgelöst wird. So vermeiden Sie auch, dass Sie Schließungen verwalten müssen.


6

Wenn changeValue2es aufgerufen wird, wird der Anfangszustand gehalten, so dass der Zustand in den Anfangszustand zurückkehrt und dann die value2Eigenschaft geschrieben wird.

Wenn das nächste Mal danach changeValue2aufgerufen wird, enthält es den Status {value1: "1", value2: "new 2"}, sodass die value1Eigenschaft überschrieben wird.

Sie benötigen eine Pfeilfunktion für den setStateParameter.

const Test = () => {
  const [state, setState] = React.useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState(prev => ({ ...prev, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState(prev => ({ ...prev, value2: "new 2" }));
  };

  return (
    <React.Fragment>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </React.Fragment>
  );
};

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


3

Was passiert ist, dass beide changeValue1und changeValue2den Status aus dem Render sehen, in dem sie erstellt wurden. Wenn Ihre Komponente zum ersten Mal gerendert wird, sehen diese beiden Funktionen:

state= {
  value1: "1",
  value2: "2"
}

Wenn Sie auf die Schaltfläche klicken, changeValue1wird zuerst aufgerufen und der Status wird {value1: "new1", value2: "2"}wie erwartet geändert .

Jetzt wird nach 1 Sekunde changeValue2aufgerufen, aber diese Funktion sieht immer noch den Anfangszustand ( {value1; "1", value2: "2"}). Wenn diese Funktion den Zustand auf folgende Weise aktualisiert:

setState({ ...state, value2: "new 2" });

Sie sehen am Ende : {value1; "1", value2: "new2"}.

Quelle

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.