web-dev-qa-db-ja.com

useState setメソッドが変更をすぐに反映しない

フックを学習しようとしていますが、useStateメソッドを使用すると混乱します。配列の形で状態に初期値を割り当てています。 useStateのsetメソッドは、spread(...)または_without spread operator_を使用しても機能しません。状態に設定したいデータを呼び出してフェッチする別のPCでAPIを作成しました。

これが私のコードです:

_import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const StateSelector = () => {
  const initialValue = [
    {
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    }
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {
        //const response = await fetch(
        //`http://192.168.1.164:5000/movies/display`
        //);
        //const json = await response.json();
        //const result = json.data.result;
        const result = [
          {
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          },
          {
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          }
        ];
        console.log(result);
        setMovies(result);
        console.log(movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
_

setMovies(result)およびsetMovies(...result)が機能していません。ここでいくつかの助けを使うことができます。

結果変数がmovies配列にプッシュされることを期待しています。

92
Pranjal

_React.Component_または_React.PureComponent_を拡張して作成されたクラスコンポーネントのsetStateと同様に、useStateフックによって提供されるアップデーターを使用した状態の更新も非同期であり、すぐには反映されず更新されますがトリガーされます再レンダリング

_setMovies(result);
console.log(movies) // movies here will not be updated
_

状態の更新時にアクションを実行する場合は、クラスコンポーネントでcomponentDidUpdateを使用する場合と同様に、useEffectフックを使用する必要があります。useStateによって返されるセッターにはコールバックパターンがないためです。

_useEffect(() => {
    // action on update of movies
}, [movies]);
_

状態を更新する構文に関する限り、setMovies(result)は、状態内の以前のmovies値を非同期リクエストから利用可能な値に置き換えます

ただし、応答を既存の値とマージする場合は、次のようなスプレッド構文を正しく使用するとともに、状態更新のコールバック構文を使用する必要があります。

_setMovies(prevMovies => ([...prevMovies, ...result]));
_
126
Shubham Khatri

前の回答への追加の詳細

ReactのsetStateis非同期(クラスとフックの両方)であり、観測された動作を説明するためにその事実を使用するのは魅力的ですが、_ではありません理由発生します。

TLDR:理由は、不変のconst値の周りの closure スコープです。


ソリューション:

  • (ネストされた関数内ではなく)render関数で値を読み取ります。

    _useEffect(() => { setMovies(result) }, [])
    console.log(movies)
    _
  • 変数を依存関係に追加します(そして react-hooks/exhaustive-deps eslintルールを使用します):

    _useEffect(() => { setMovies(result) }, [])
    useEffect(() => { console.log(movies) }, [movies])
    _
  • 可変参照を使用します(上記が不可能な場合):

    _const moviesRef = useRef(initialValue)
    useEffect(() => {
      moviesRef.current = result
      console.log(moviesRef.current)
    }, [])
    _

発生理由の説明:

非同期が唯一の理由である場合、await setState()が可能です。

ちなみに、propsstateはどちらも 1回のレンダリング中に不変であると仮定されています

_this.state_を不変であるかのように扱います。

フックでは、constキーワードで定数値を使用することにより、この仮定が強化されます。

_const [state, setState] = useState('initial')
_

値は2つのレンダー間で異なる可能性がありますが、レンダー自体およびすべての closures 内で一定のままです(レンダーが終了した後でも長く存続する関数、たとえばuseEffect、イベントハンドラー、PromiseまたはsetTimeout内)。

次の偽物を検討してくださいが、synchronous、Reactのような実装:

_// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => {
  internalState = updateFn(internalState)
  renderAgain()
}

const useState = (defaultState) => {
  if (!internalState) {
    internalState = defaultState
  }
  return [internalState, setState]
}

const render = (component, node) => {
  const {html, handleClick} = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick
}

// test:

const MyComponent = () => {
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => {
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  }
  
  return {
    html: `<button>${x}</button>`,
    handleClick
  }
}

const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()_
_<div id="root"></div>_
39
Aprillion