← 전체 글로 돌아가기

웹 개발

저장 버튼을 두 번 누르지 못하게 막는 가장 간단한 방법

폼 제출 중에 버튼을 비활성화하는 것만으로 중복 요청을 막을 수 있다. 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 처리와 이중 방어 가드를 함께 두면 어떤 환경에서든 중복 제출이 막힌다.