この記事ではタイトル通り「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の更新が反映されるタイミングに関してはReact初心者はつまずきやすいと思います。
(僕もハマりました…)
補足で書いておくと、3回目のsetValueList
の後にconsole.log(valueList);
を書いて実行しても空のオブジェクトがコンソールに出力されます。
最後に
上手くいかないケースも交えて長々と書きましたが、結論はこう。
当分はReactについての記事を書いていこうかなと思います。(たまにLaravel書くかも)
コメント