웹 개발
저장 버튼을 두 번 누르지 못하게 막는 가장 간단한 방법
폼 제출 중에 버튼을 비활성화하는 것만으로 중복 요청을 막을 수 있다. isSubmitting 상태 하나면 충분하다.
저장 버튼을 빠르게 두 번 누르면 API 요청이 두 번 날아가는 경우가 있다. 서버에서 중복 처리가 되면 데이터가 두 번 생성되거나, 낙관적 업데이트를 쓰는 경우 상태가 꼬인다.
React에서 가장 단순한 방법은 isSubmitting 상태를 만들고, 제출 중에 버튼을 disabled로 만드는 것이다.
기본 구현
function SaveButton() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSave() {
if (isSubmitting) return; // 이중 방어
setIsSubmitting(true);
try {
await saveData();
} finally {
setIsSubmitting(false);
}
}
return (
<button onClick={handleSave} disabled={isSubmitting}>
{isSubmitting ? '저장 중...' : '저장'}
</button>
);
}
disabled 속성이 있으면 클릭 이벤트 자체가 발생하지 않는다. if (isSubmitting) return 줄은 혹시 모를 경우를 위한 이중 방어다.
form 태그를 쓰는 경우
<form>을 쓴다면 onSubmit에서 처리한다.
function SaveForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (isSubmitting) return;
setIsSubmitting(true);
try {
const data = new FormData(e.currentTarget as HTMLFormElement);
await saveData(Object.fromEntries(data));
} finally {
setIsSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input name="title" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '저장 중...' : '저장'}
</button>
</form>
);
}
react-hook-form을 쓰는 경우
formState.isSubmitting을 그대로 쓸 수 있다.
const { handleSubmit, formState: { isSubmitting } } = useForm();
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '저장 중...' : '저장'}
</button>
useForm이 제출 상태를 자동으로 관리하므로 useState를 따로 만들 필요가 없다.
버튼 텍스트 변경이 중요한 이유
disabled만으로는 사용자가 버튼이 왜 안 눌리는지 모른다. 저장 중...으로 텍스트가 바뀌면 요청이 처리 중이라는 걸 알 수 있다. 요청이 느린 경우 스피너를 추가하는 것도 방법이다.
모바일에서는 터치 이벤트가 두 번 발생하는 경우도 있다. disabled 처리와 이중 방어 가드를 함께 두면 어떤 환경에서든 중복 제출이 막힌다.