실수로 혹은 의도적으로 버튼을 연속적으로 클릭하는 것을 버튼 따닥이라고 한다. 버튼을 계속 클릭하면 이벤트가 연속으로 호출되는 현상이다. 결제 페이지를 보니 우리 웹페이지에서도 이런 현상을 막지 않았다. 다음 버튼을 정말 빠른 속도로 연속해서 누르면 (신용카드, Aba pay)의 경우 호출 창이 실행되기 전까지 이벤트가 여러게 찍힌다.

예전에 본 기사 중에 백엔드 그리고 프론트엔드 개발자 둘 다 이 현상을 막지 않아서 유저들이 중복으로 게임 이벤트 보상을 받은 사례가 있었다는 일이 떠오른다. 그리고 나도…
우선 가장 쉬운 방법으로는 아래와 같은 throttle 로직을 적용하는 방법이 있다. 아래 로직은 첫번째 함수만 실행하고 그 이후의 함수는 delay가 지날 때 까지 실행이 되지 않는다.
function throttle(func, delay) {
let isThrottled = false;
return (...args) => {
if (isThrottled) return;
isThrottled = true;
func(...args); // 첫 실행은 즉시
setTimeout(() => {
isThrottled = false; // delay 후 다시 실행 가능
}, delay);
};
}
당장 중복클릭은 막을 수 있다. 하지만 throttle의 경우 delay 라는 고정된 값에 의존하기 때문에 문제가 있다. 예를 들면 api 호출이 delay 타임 이상으로 걸리면 그 이후 시점에는 중복 호출이 가능해진다.
다행스럽게도 react-query는 isPending, isLoading 비동기 상태를 확인해 줄 수 있는 기능을 제공하고 있다.
아래 처럼 isPending이나 isLoading으로 처리를 하면 문제가 해결되나 했지만 어림도 없었다.
import { useMutation } from "@tanstack/react-query";
const handleSubmit = () => {
if (isPending) {
console.warn("이미 요청 중입니다!");
return;
}
mutate();
};
const { mutate, isPending } = useMutation(fetchData);
<button onClick={handleSubmit} disabled={isPending}>
{isPending ? "처리 중..." : "결제"}
</button>;
React query의 mutation isPending은 React와 같은 Observer 패턴을 적용하고 있어 즉각적인 상태변화를 잡는데 어려움이 있으며, 이벤트를 한번에 처리하는 배칭처리가 적용되어 상태를 비동기적으로 업데이트가 된다.
즉, mutation의 isPending은 실제 요청의 진행 상황과 React의 상태 업데이트 타이밍 사이에 지연이 발생할 수 있고, 클릭 간의 간격이 있으면 중복호출을 방지 할 수 있지만, 앞서 말한 따닥을 방지하기 위한 근본적인 해결법은 아니다. 하지만 언제나 그렇듯 나보다 유능한 개발자 분께서 이러한 문제를 해결했을 것이라 생각한다. ****아래 포스팅 링크는 ispending의 문제점과 해결방안을 제시하고 있다
https://www.xionwcfm.com/posts/frontend/single-flight