Reactアプリの多重クリックを防止する最適解とは?

Reactアプリの多重クリック防止について考えてみたから、共有かたがた備忘のために記しておきたい。

多重クリックとは、ボタン等のユーザーアクションから発せられたイベントが重複処理されることを指す。お問い合わせフォームの送信が想像しやすいだろうか。入力した内容が重複して送信される問題で、まったくもって無駄なリクエストである。

そんなことからアプリケーション全般に多重クリックや二重送信を防止する施策が求められ、それぞれの要件にあった手法で実装されている。無論、Reactアプリも、その例に漏れない。

多重クリックを防止する方法

Webアプリの多重クリック防止のデザインパターンに、非活性化させる手段を思い出す。フラグ判定で処理中は非活性化させるというものだ。これを以下に再現してみた。

function Form() {
  const [processing, setProcessing] = useState(false);

  const handleSubmit = e => {
    e.preventDefault();

    // 処理中(true)なら非同期処理せずに抜ける
    if (processing) return;

    // 処理中フラグを上げる
    setProcessing(true);

    // 疑似非同期処理
    setTimeout(() => {
      // 処理中フラグを下げる
      setProcessing(false);
    }, 5000);
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">送信</button>
    </form>
  );
}

コンポーネント冒頭の const [processing, setProcessing] = useState(false); で、state定義している。これをフラグに見立て if (processing) return; で判定するといった具合のものだ。

多重クリック防止のアプローチについては納得いくものだけれど、state を活用したフラグ判定は不適当じゃないか、というのが肝心なところ。

問題

state を更新することで、コンポーネントの再レンダリングが強制される。つまり上記ソースコードならば、コンポーネントをマウントしたときと非同期処理が走ったときレンダリングをスケジュールしている。画面上の更新を求めていないにも関わらず、不要にレンダリングをすることはパフォーマンスに影響を与えかねない。

代替案

state によるフラグ判定の代替案として、以下に改修してみた。

function Form() {
  const processing = useRef(false);

  const handleSubmit = e => {
    e.preventDefault();

    // 処理中(true)なら非同期処理せずに抜ける
    if (processing.current) return;

    // 処理中フラグを上げる
    processing.current = true;

    // 疑似非同期処理
    setTimeout(() => {
      // 処理中フラグを下げる
      processing.current = false;
    }, 5000);
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">送信</button>
    </form>
  );
}

useStateuseRef に変わり state の更新を排除したことに気づくと思う。refオブジェクトは一般にDOM参照に活用されている印象があるが、公式で次のように述べている。

はい! useRef() フックは DOM への参照を保持するためだけにあるのではありません。“ref” オブジェクトは汎用のコンテナであり、その current プロパティの値は書き換え可能かつどのような値でも保持することができますので、クラスのインスタンス変数と同様に利用できます。

https://ja.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

またコンポーネントが存在しているあいだ書き換え可能なオブジェクトとして保ち続けて、その値を変更しても再レンダリングが発生しない。

したがって Refs の活用は、簡易な多重クリック防止のデザインパターンに相性がよい。再レンダリングによるパフォーマンス悪化や思いも寄らない副作用の回避に一助することだろう。

とはいえ Refs 活用一辺倒でよいというものでもない。たとえばボタンクリックと同時にdisabled属性を付与したりボタンテキストを変えたりする場合、再レンダリングが必要で、適宜コンテンツに最適な対応が求められそうだ。

まとめ

Reactアプリの多重クリックを防止する最適解についての共有だった。

わたし自身、Refs の真価に気づかず、Stateを活用しているケースが見受けられた。一方でフォームヘルパーを使うケースもあるしフェッチライブラリならば処理状態を返却してくれる。一概に当該ソリューションが最適解というわけではないけれど、ユースケースとして覚えていて損はないと思う。

このエントリーが、あなたのクリエイティビティを刺激するものであると期待したい。

React Hooksに対応した複数のonChangeハンドラを処理する書き方

コメントを残す

メールアドレスが公開されることはありません。*がついている欄は必須項目です。

日本語が含まれない投稿は無視されますのでご注意ください。