0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

汎用の連打防止付き async フック

Last updated at Posted at 2025-11-14

記事は後ほど書きます。
振り返り用にメモ

✅メモリリーク対策追加(対応)
✅注意:asyncFunctionが変更されるたびにexecute関数が再作成され、意図しない動作が起こる可能性あり(対応)

import { useState, useCallback, useRef, useEffect } from "react";

/**
 * 非同期アクションフック
 * - 連打防止
 * - ローディング状態管理
 * - エラーハンドリング
 *
 * @param asyncFunction 非同期関数
 * @returns [実行関数, isLoading]
 *
 * @example
 * const [handleSubmit, isLoading] = useAsyncAction(async () => {
 *   await api.submit();
 * });
 */
export function useAsyncAction<T extends (...args: any[]) => Promise<any>>(
  asyncFunction: T
): [T, boolean] {
  const [isLoading, setIsLoading] = useState(false);
  const isProcessing = useRef(false);

  //asyncFunction が変わった場合に対応するための ref
  const asyncFunctionRef = useRef(asyncFunction);

  // マウント状態を追跡する ref
  const isMountedRef = useRef(true);

  // asyncFunction が変更されたら ref を更新
  useEffect(() => {
    asyncFunctionRef.current = asyncFunction;
  }, [asyncFunction]);

  useEffect(() => {
    isMountedRef.current = true;

    return () => {
      isMountedRef.current = false;
    };
  }, []);

  const execute = useCallback(
    (async (...args: Parameters<T>) => {
      // アンマウント済みなら何もしない
      if (!isMountedRef.current) {
        if (__DEV__) {
          console.log(
            "コンポーネントがアンマウント済みのため、処理をスキップ"
          );
        }
        return;
      }
      // 連打防止
      if (isProcessing.current) {
        if (__DEV__) {
          console.log("処理中のため、クリックを無視");
        }
        return;
      }

      // 実行開始
      isProcessing.current = true;
      setIsLoading(true);

      try {
        const result = await asyncFunctionRef.current(...args);
        return result;
      } catch (error) {
        // エラー
        throw error;
      } finally {
        // 実行完了
        // マウント済みの場合のみ状態更新
        if (isMountedRef.current) {
          setIsLoading(false);
        }
        isProcessing.current = false;
      }
    }) as T,
    []
  );

  return [execute, isLoading];
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?