🙆♀️
【React】1イベントハンドラ内でstateの状態を複数回更新したいとき
課題の概要
- 1メソッド内の処理の進行状況をuseStateで管理したかった
- しかしReactは1イベントハンドラ内の状態の更新は最後の1つしか認識できない
- よって進行状況ではなく「完了した」という時点の情報しか送れなかった
- 公式ドキュメントでは以下のように、更新できそうに思わせる内容もあるが、結論できない
- 直前の state に応じて更新する方法
- https://ja.react.dev/reference/react/useState#updating-state-based-on-the-previous-state
- 保持している値の更新自体はできても、他コンポーネントに引き渡されたりするのは最後の状態だけ
- 中間の状態を別コンポーネントに送るなどはできない
- 直前の state に応じて更新する方法
課題の詳細
- canvasを用いた画像編集機能を改修していた
- 画像の描画中に保存ボタンが押下されるとエラーが起きるため、描画中はボタンを非活性にしたかった
- 元々の実装でも、画像が読み込まれている時だけ保存できるように制御は行なっていた
- しかし今回、データ読み込み後の描画にかかる時間が長くなるような機能追加を行った
- 描画が
window.requestAnimationFrame
による非同期処理だったため、描画に時間がかかる画像や環境では「読み込まれているが描画されていない状態」が生まれていた - そのタイミングで保存ボタンを押下すると画像が消えてしまった
- 元々画像を登録している場合でも描画に時間がかかった場合は上記エラーが起きて画像が削除されてしまうことから、UX上許容できなかった
- よって初期値falseのuseState
[isDrawing, setIsDrawing]
を定義し、setIsDrawing
を画像描画処理メソッドdraw()
に渡した - 非同期処理であるdraw開始時に
setIsDrawing(true)
とし、非同期処理終了時にsetIsDrawing(false)
とした - ボタンには
disabled={isDrawing}
を設定- isDrawingがtrueのときはdisabledをtrueにする
- 結果、draw()目線では
isDrawing
の値は変わっていた - しかし描画コンポーネントに渡るのは最後に実行された
setIsDrawing(false)
だけだったため、常にボタンが活性だった
解き方の概要
結論、useStateは使わず、draw()が完了した状態かだけを取得するgetterを定義した。
Reactの仕様上、処理の途中の状態で値を送ることは不可能。出来るなら本当に教えて欲しい。
- 画像描画完了を意味する
imageDrew: boolean
を定義 - draw()の非同期処理の最後で
imageDrew = true
と設定 -
public get isImageDrew
という、imageDrew
をreturnするgetterを定義 - 保存ボタンでは
disable={!editor.isImageDrew}
としてisImageDrewを一度だけ呼び出す- ここでgetterを複数回呼び出せるようにしたり、useEffectで更新できるようにしたりしても意味なかった
- editorからの他のgetterの呼び出しを含めた時もうまく動かなかったので、おそらく一度しか取りに行けない(要精緻化)
- しかし上記だと画像がdrew()された状態でしか保存できず、画像を削除して保存したいときに制御できなかった
- よって「画像が選択された上で、isDrewではないとき」を指定できるよう
disable={!editor.isImageDrew && isUploadImage}
へと変更した
今後へのメモ
- 沼った時は全く別のやり方が出来ないか試しまくろう
- 「この方法で出来そうだが難しい」みたいに感じるときは大体出来ないとき
- 「保守性のためにも、もっと簡単なやり方を探そう」という気持ちに切り替える
Discussion