はじめに
setStateとは?
React hooksの代表的な機能で、主にクラスコンポーネントにおいて用いられるメソッド。
Reactを使うなら useState
とthis.setState()
は避けれない(やってることは同じ..だと思う)
なぜこんなことを考えたのか
とあるコードにおいて新しい配列をstateに反映し、その直後に更新したstate変数の配列をいじるようなことになった。
以下がその例
//とあるクラスコンポーネント内
//選択したタスクアイテムを編集可能にしてフォーカスするためのメソッド
changeItemEditable(id:string){
const items: CheckListItem[] = this.state.checklist.map((item:CheckListItem) => {
return {
id: item.id,
name: item.name,
isChecked: item.isChecked,
isReadOnly: item.id === id?!item.isReadOnly:item.isReadOnly
}
})
const target = document.getElementById(`text-input-id=${id}`);
target?target.focus():undefined
this.setState({checklist: items})
}
//新しいタスクを追加するメソッド
addNewTask() {
const tasks = this.state.checklist
const task = new Task(tasks);//タスクの追加・削除・検索などを管理するクラス
const newTask = task.addTask()
this.setState({checklist: [...tasks, newTask]});
this.changeItemEditable(newTask.id);
}
このコードにおいて
- タスクを追加
- 空のタスクが追加され、編集するために追加後に自動フォーカス
を実現したかった。
しかしうまくいかなかった。
うまくいかなかった原因と解決策
うまくいかなかった原因
setStateは非同期関数だった
つまり上記の例でいくと先にthis.changeItemEditable
が実行されていたのだ。
そのためchangeItemEditable
メソッド内のsetState(更新前)の実行と、addNewTaskのsetState(更新)が同時に発火し、更新した直後に更新前に戻されるということが発生したのが原因だった。
解決策
setStateは非同期関数である
このことに着目すると答えは簡単でasync/await
を使えばいい
以下が修正後のコード
changeItemEditable(id:string){
const items: CheckListItem[] = this.state.checklist.map((item:CheckListItem) => {
return {
id: item.id,
name: item.name,
isChecked: item.isChecked,
isReadOnly: item.id === id?!item.isReadOnly:item.isReadOnly
}
})
const target = document.getElementById(`text-input-id=${id}`);
target?target.focus():undefined
this.setState({checklist: items})
}
async addNewTask() {
const tasks = this.state.checklist
const task = new Task(tasks);
const newTask = task.addTask()
await this.setState({checklist: [...tasks, newTask]});
await this.changeItemEditable(newTask.id);
}