useStateフックセッターが誤って状態を上書きする


31

ここに問題があります:ボタンのクリックで2つの関数を呼び出そうとしています。どちらの関数も状態を更新します(useStateフックを使用しています)。最初の関数はvalue1を「新しい1」に正しく更新しますが、1秒後に(setTimeout)2番目の関数が起動し、値2を「新しい2」に変更します。これは、value1を「1」に戻します。なんでこんなことが起こっているの?前もって感謝します!

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;

最初の状態を記録できますchangeValue2か?
DanStarns

1
オブジェクトを2つの個別の呼び出しに分割するuseStateか、代わりにを使用することを強くお勧めしますuseReducer
Jared Smith、

はい-2番目。useState()を2回呼び出すだけです
Pedersen

const [state, ...]、そしてそれをセッターで参照します...常に同じ状態を使用します。
Johannes Kuhn

最善の行動:useState各「変数」に対して1つずつ、2つの別々の呼び出しを使用します。
Dima Tisnek

回答:


30

閉鎖地獄へようこそ。この問題setStateは、stateが呼び出されるたびに新しいメモリ参照を取得するために発生しますが、関数changeValue1changeValue2は、閉鎖のために古い初期state参照を保持します。

setStatefrom を確実にし、最新の状態changeValue1changeValue2取得するソリューションは、コールバックを使用することです(以前の状態をパラメーターとして持っています)。

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

  // ...
};

この閉鎖の問題に関するより広範な議論は、ここここにあります


useStateフックを使用したコールバックは、ドキュメントに記載されていない機能のようですが、それは機能しますか?
HMR、

@HMRはい、動作し、別のページに記載されています。こちらの「機能の更新」セクションをご覧ください:reactjs.org/docs/hooks-reference.html(「新しい状態が以前の状態を使用して計算されている場合、関数をsetStateに渡すことができます」)
Alberto Trindade Tavares

1
@AlbertoTrindadeTavaresはい、ドキュメントも調べていましたが、何も見つかりませんでした。答えてくれてありがとう!
Bartek、

1
最初の解決策は、「簡単な解決策」ではなく、正しい方法です。2つ目は、コンポーネントがシングルトンとして設計されている場合にのみ機能します。それでも、状態は毎回新しいオブジェクトになるため、それについてはわかりません。
-Scimonster

1
@AlbertoTrindadeTavaresありがとうございます!いい

19

関数は次のようになります。

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

したがって、アクションが発生したときに前の状態を使用することにより、現在の状態の既存のプロパティが欠落していないことを確認します。また、したがって、クロージャを管理する必要がなくなります。


6

ときにchangeValue2呼び出され、状態のターンはinital状態に戻りので、初期状態が保持され、その後、value2プロパティが書かれています。

その後次回changeValue2呼び出されたときに状態を保持している{value1: "1", value2: "new 2"}ため、value1プロパティは上書きされます。

setStateパラメータには矢印関数が必要です。

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

何が起こっているかというと、changeValue1とで作成されたレンダーからchangeValue2の状態わかるため、コンポーネントが初めてレンダーしたときに、これらの2つの関数は次のように見えます。

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

ボタンをクリックすると、changeValue1最初にが呼び出され、状態が{value1: "new1", value2: "2"}期待どおりに変更されます。

ここで、1秒後、changeValue2が呼び出されますが、この関数はまだ初期状態({value1; "1", value2: "2"})を参照しているため、この関数が状態を次のように更新すると、

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

最終的には次のようになります{value1; "1", value2: "new2"}

ソース

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