【React】1回のイベントでオブジェクト型のstateの複数のプロパティを更新する

この記事ではタイトル通り「1回のイベントでオブジェクト型のstateの複数のプロパティを更新する」方法をまとめます。

完全に個人的な備忘録ですが、誰かの役にたてば嬉しいなという思いもあって残しておきます。

state(useState含む)については別に記事にも書いています。

目次

1回のイベントでオブジェクト型のstateの複数のプロパティを更新する方法

以下のような画面で説明します。

実装したい仕様は以下。

  • value1、value2、value3のテキストフォームに値を入力する
  • その状態で「state一括更新」ボタンを押す
  • ボタンを押すとオブジェクト型のstate(valueList)のvalue1、value2、value3のプロパティにテキストフォームの入力値で更新する
  • 「state(valueList)の値」の下にはstateの各プロパティの値を表示するのでそこにテキストフォームの入力値を表示する

まず、うまくいく実装から書きます。(わかりやすいように敢えて冗長に書いている箇所もあります)

import { useState } from "react";

type ValueList = {
  value1: string;
  value2: string;
  value3: string;
};

export default function App() {
  // オブジェクト型のstateを定義
  const [valueList, setValueList] = useState<ValueList>({
    value1: "",
    value2: "",
    value3: "",
  });

  const handleClick = () => {
    const inputValue1 = (document.getElementById("value1") as HTMLInputElement).value;
    const inputValue2 = (document.getElementById("value2") as HTMLInputElement).value;
    const inputValue3 = (document.getElementById("value3") as HTMLInputElement).value;
    setValueList({
      ...valueList,
      value1: inputValue1,
      value2: inputValue2,
      value3: inputValue3,
    });
  };

  return (
    <>
      <div>
        <span style={{ marginRight: "5px" }}>value1</span>
        <input id="value1" />
      </div>
      <div>
        <span style={{ marginRight: "5px" }}>value2</span>
        <input id="value2" />
      </div>
      <div>
        <span style={{ marginRight: "5px" }}>value3</span>
        <input id="value3" />
      </div>
      <div style={{ marginTop: "10px" }}>
        <button onClick={handleClick}>state一括更新</button>
      </div>
      <div>
        <p>state(ValueList)の値</p>
        <p>value1:{valueList.value1}</p>
        <p>value2:{valueList.value2}</p>
        <p>value3:{valueList.value3}</p>
      </div>
    </>
  );
}

state更新の部分はhandleClickの以下です。

  setValueList({
    ...valueList,
    value1: inputValue1,
    value2: inputValue2,
    value3: inputValue3,
  });

スプレッド構文で更新したプロパティと値をカンマ区切りで複数設定するだけです。

実際にこのような動作になります。

ちゃんとstateが一括で更新されているのが分かります。

いざ実装方法がわかれば全然難しくないのですが、スプレッド構文は第二引数までしか持てないという謎の先入観があったので以下のようなコードを書いてつまずきました…

  const handleClick = () => {
    const inputValue1 = (document.getElementById("value1") as HTMLInputElement).value;
    const inputValue2 = (document.getElementById("value2") as HTMLInputElement).value;
    const inputValue3 = (document.getElementById("value3") as HTMLInputElement).value;
    setValueList({ ...valueList, value1: inputValue1 });
    setValueList({ ...valueList, value2: inputValue2 });
    setValueList({ ...valueList, value3: inputValue3 });
  };

このように1つずつset関数で更新するとこうなります。

なぜこのようなことになってしまうのかというとstateが更新されるタイミングに関係します。

では以下のようにレンダリング時のタイミング、「state一括更新」ボタンを押したタイミングに実行される位置にconsole.logを入れた感じで書いてみます。

import { useState } from "react";

type ValueList = {
  value1: string;
  value2: string;
  value3: string;
};

export default function App() {
  // オブジェクト型のstateを定義
  const [valueList, setValueList] = useState<ValueList>({
    value1: "",
    value2: "",
    value3: ""
  });

  // 追加
  console.log('レンダリングします!!')

  const handleClick = () => {
    const inputValue1 = (document.getElementById("value1") as HTMLInputElement)
      .value;
    const inputValue2 = (document.getElementById("value2") as HTMLInputElement)
      .value;
    const inputValue3 = (document.getElementById("value3") as HTMLInputElement)
      .value;
    setValueList({ ...valueList, value1: inputValue1 });
   // 追加
    console.log(valueList);
    setValueList({ ...valueList, value2: inputValue2 });
    // 追加
    console.log(valueList);
    setValueList({ ...valueList, value3: inputValue3 });
  };

  return (
    <>
      <div>
        <span style={{ marginRight: "5px" }}>value1</span>
        <input id="value1" />
      </div>
      <div>
        <span style={{ marginRight: "5px" }}>value2</span>
        <input id="value2" />
      </div>
      <div>
        <span style={{ marginRight: "5px" }}>value3</span>
        <input id="value3" />
      </div>
      <div style={{ marginTop: "10px" }}>
        <button onClick={handleClick}>state一括更新</button>
      </div>
      <div>
        <p>state(valueList)の値</p>
        <p>value1:{valueList.value1}</p>
        <p>value2:{valueList.value2}</p>
        <p>value3:{valueList.value3}</p>
      </div>
    </>
  );
}

この状態で再度「state一括更新ボタン」を押すとコンソールにはこのような表示になります。

  • 初回のレンダリング時に「レンダリングします!!」と出力
  • 「state一括更新ボタン」を押した時にhandleClickの中の処理でset関数後のstateを出力するもいずれも空のオブジェクトが出力されている×2
  • stateが更新されたのでコンポーネントが再レンダリングされ、「レンダリングします!!」と出力

handleClickメソッドの中でsetValueListでstateを3回更新する処理を実行していますが、1回目、2回目を終えてもstateは更新されておらず初期値のままです。

stateはコンポーネントが再レンダリングされた段階で更新した値が反映されるので、3回目のsetValueListの内容だけが反映されて「こんばんわ」だけが画面に表示されてしまっていたということですね。

stateの更新が反映されるタイミングに関してはReact初心者はつまずきやすいと思います。
(僕もハマりました…)

補足で書いておくと、3回目のsetValueListの後にconsole.log(valueList);を書いて実行しても空のオブジェクトがコンソールに出力されます。

最後に

上手くいかないケースも交えて長々と書きましたが、結論はこう。

1回のイベントでオブジェクト型のstateの複数のプロパティを更新するためには1回のset関数でスプレッド構文に複数のキーとバリューも組み合わせをカンマ区切りで書く

当分はReactについての記事を書いていこうかなと思います。(たまにLaravel書くかも)

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

Webエンジニア←KHI(プラント事業の技術職)←大学院(機械工学)

PHP/Laravel/TypeScript/React/Next.js(Vue.js/Nuxt.jsは少し)
バックエンド歴の方が長いです。

神戸で「つながる勉強会」を運営↓
https://tsunagaru-kobe.connpass.com/

コメント

コメントする

目次
閉じる