🙆‍♀️

【React】1イベントハンドラ内でstateの状態を複数回更新したいとき

2024/10/28に公開

課題の概要

  • 1メソッド内の処理の進行状況をuseStateで管理したかった
  • しかしReactは1イベントハンドラ内の状態の更新は最後の1つしか認識できない
  • よって進行状況ではなく「完了した」という時点の情報しか送れなかった
  • 公式ドキュメントでは以下のように、更新できそうに思わせる内容もあるが、結論できない

課題の詳細

  • 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