← 전체 글로 돌아가기

Next.js

React 폼의 저장 버튼을 두 번 눌러 중복 요청을 만든 실수 정리

버튼 비활성화만 믿었다가 빠른 두 클릭을 막지 못한 경험에서 클라이언트와 서버의 역할을 나눠 정리했습니다.

두 번 클릭은 흔한 입력이다

게시글 작성 화면에서 저장 직후 변화가 늦자 같은 제목의 POST가 두 건 생겼다. 사용자가 이상한 행동을 한 것이 아니라 네트워크가 느리면 누구나 다시 누른다. disabled 상태가 화면에 반영되기 전 같은 이벤트가 들어올 수 있다는 점을 놓쳤다.

화면에서는 먼저 잠갔다

표시용 state와 경쟁 조건 방지용 ref를 나눴다. 요청 시작 순간 ref를 바꾸므로 빠른 연속 클릭도 막을 수 있다.

const submitting = useRef(false)
const [pending, setPending] = useState(false)

async function onSubmit(data: FormData) {
  if (submitting.current) return
  submitting.current = true
  setPending(true)
  try {
    await createPost(data)
  } finally {
    submitting.current = false
    setPending(false)
  }
}

버튼에는 disabled={pending}과 저장 중 문구를 넣었다. 실패해도 finally에서 잠금이 풀리도록 한 것이 중요했다.

서버가 마지막 방어선이다

클라이언트 잠금은 새로고침, 다른 탭, 재전송을 막지 못한다. 중복이 치명적인 요청은 idempotency key를 받고, DB에는 slug나 주문 번호 같은 유니크 제약을 둬야 한다.

const saved = await db.request.findUnique({ where: { key } })
if (saved) return Response.json(saved.response)

제출 전 점검표

  • Enter 제출과 빠른 연속 클릭을 시험했는가?
  • 실패 시 버튼이 다시 활성화되는가?
  • 서버가 같은 요청을 안전하게 처리하는가?

두 번 눌렀다는 말을 경고로 보지 않고, 중복에도 안전한 흐름을 만들 계기로 삼게 됐다.