본문으로 건너뛰기

useAsyncLock

중복 요청을 방지하기 위한 커스텀 훅입니다. 비동기 함수 실행 시 이미 실행 중이라면, 새로운 실행을 막고 undefined를 반환합니다. 이를 통해 버튼 연타 등으로 인한 중복 API 요청을 안전하게 차단할 수 있습니다.

🔗 사용법

const { runWithLock, loading, isLockedRef } = useAsyncLock();

매개변수

useAsyncLock 자체는 별도 매개변수가 없습니다.
대신 runWithLock을 호출할 때 두 번째 인자로 onError를 전달할 수 있습니다.

runWithLock

이름타입설명필수 여부
fn() => Promise<T>실행할 비동기 함수필수
onError(err: unknown) => void실행 중 에러가 발생했을 때 호출되는 핸들러선택

반환값

{ runWithLock, loading, isLockedRef }

이름타입설명
runWithLockPromise<T> or undefinedfn을 실행하면서 중복 실행을 막아주는 함수
loadingboolean현재 실행 중인지 여부
isLockedRefReact.MutableRefObject<boolean>내부 잠금 상태를 직접 확인할 수 있는 ref 객체

✅ 예시

  • 버튼을 여러 번 눌러도 실행 중에는 한 번만 동작합니다.
  • 에러가 발생하면 onError 콜백이 실행됩니다.
  • loading 상태를 이용해 버튼을 disable 처리할 수 있습니다.
import { useAsyncLock } from '@/hooks/useAsyncLock';

function DeleteButton({ id }: { id: number }) {
const { runWithLock, loading } = useAsyncLock();

const deleteItem = async () => {
const res = await fetch(`/api/items/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error('삭제 실패');
alert('삭제 완료!');
};

const handleClick = () => {
runWithLock(deleteItem, (err) => {
if (err instanceof Error) {
alert(`삭제 실패: ${err.message}`);
} else {
console.error('Unexpected error:', err);
}
});
};

return (
<button onClick={handleClick} disabled={loading}>
{loading ? '삭제 중...' : '삭제하기'}
</button>
);
}