nprimem
@nprimem (与太郎)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Reactでは、いつstateは更新されるのですか?

解決したいこと

ここに解決したい内容を記載してください。

例)
なぜ以下の二つの場合、sample1はconsole.logのタイミングで更新されておらず、
sample2では更新されているのですか?

import React, { useState } from 'react';

type sampleType = {
  a:number,
  b:number
}

function App() {

  const [sample, setSample] =useState<sampleType>(
    {a:1, b:0}
  )

  const clickButton1 = () => {
    console.log(sample);
    setSample({a:sample.a+=1, b:sample.b+=1})
    console.log(sample)
  }

  const clickButton2 = () => {
    console.log(sample);
    setSample({a:2, b:2})
    console.log(sample)
  }

  return (
    <>
    <button onClick={clickButton1}>ここを押して1</button>
    <button onClick={clickButton2}>ここを押して2</button>
    </>
  );
}

export default App;


clickButton1のとき、ボタンを押した場合

(1,0)
(1,0)

clickButton2の場合

(1,0)
(2,1)

エラーの原因を探していたらここに行きつきました。
挙動の違いがいまいちよくわかりません。
お知恵をお貸しください

0

2Answer

まず、大前提として、ReactにおけるStateはsetStateを通してのみ更新してください。そうでないと、再レンダリングがかからなくなります。
setSample({a:sample.a+=1, b:sample.b+=1})の記述の中で、sample.aに1足されてしまっているため、sampleの値は書き変わっていて、なおかつsetSampleを呼んでいるため再レンダリングがかかっているおかげで今回はたまたま上手く動いているように見えます。これが今回の挙動の原因です。
また、二つ目にsetStateはその時点でstateが書き変わるわけではありません(同期的な更新でない)。つまり、一見 clickButton2の挙動がおかしいように見えますが、実はこちらの挙動の方が正しいです。
三つ目に、useStateの返り値の第二引数であるSetStateActionsはこのように定義されています。
type SetStateAction<S> = S | ((prevState: S) => S);
つまり、

  • 新しい状態
  • 更新する関数

の二種類を引数にとることが出来ます。これはどういうことかというと、二つ目の更新する関数を引数に渡す場合には、前の値を参照することが可能です。すなわち、このように書くことが可能です。

const clickButton1 = () => {
    setSample((prevSample) => {
      return {
        a: prevSample.a + 1,
        b: prevSample.b + 1,
      };
    });
  };

お力になれれば幸いです。

2Like

Comments

  1. @nprimem

    Questioner

    ありがとうございます。理解することが出来ました

破壊的変更(?)で直接代入してる分が反映されてそうですね

以下は等価

sample.a += 1
sample.a = sample.a + 1;

非破壊的な動作をさせるなら += じゃなくて + じゃないですか

setSample({a:sample.a+1})
1Like

Comments

  1. @nprimem

    Questioner

    なるほど。ありがとうございます。

Your answer might help someone💌