React 19 useFormStatus Returning False? I Built a SubmitButton That Fixes It
React 19 useFormStatus가 계속 false를 반환하나요? 문제를 보완하는 SubmitButton을 만들었습니다
React 19의 useFormStatus가 pending 상태를 감지하지 못하는 배치 오류를 설명하고, 접근성과 여러 제출 버튼을 고려한 SubmitButton 구성 요소를 소개합니다. 필드 잠금, 스크린 리더 알림, 폼 내부 배치 검사와 구현 한계도 함께 다룹니다.
- 주제
AI 요약
React 19의 useFormStatus()를 잘못된 구성 요소에서 호출하면 제출 중에도 pending이 계속 false로 남을 수 있습니다. 훅은 자신을 호출한 구성 요소가 직접 렌더링하는 폼이 아니라, 상위 구성 요소가 렌더링한 폼의 상태를 읽습니다. 잘못 배치해도 오류나 콘솔 경고가 뜨지 않아 버튼이 조용히 대기 상태로 들어가지 않습니다. 다만 일반 onSubmit 핸들러에서 preventDefault()만 호출하거나 action에 URL 문자열을 쓰는 폼은 추적할 Action이 없으므로, 훅 위치를 옮겨도 pending 문제가 해결되지 않습니다.
SubmitButton과 키보드 접근성
pending일 때 버튼에 disabled를 지정하면 제출 도중 포커스가 사라집니다. 키보드로 버튼에 접근한 사용자가 Enter를 눌러 제출해도 포커스가 다른 곳으로 이동할 수 있습니다. 글에서는 disabled 대신 aria-disabled를 설정하고 클릭 이벤트에서 pending 상태의 추가 제출을 막습니다. aria-busy와 data-pending도 함께 표시하며, 대기 중에는 pendingText를 보여줍니다. 실제 disabled 속성을 쓰지 않으므로 스타일은 :disabled가 아니라 aria-disabled나 data-pending을 기준으로 지정합니다.
여러 제출 버튼의 상태 구분
폼에 Save Draft와 Publish 버튼이 함께 있으면 useFormStatus의 pending은 폼 전체에 적용되므로 두 버튼이 모두 대기 상태로 바뀝니다. 브라우저가 제출 버튼의 name과 value를 FormData에 넣는 점을 이용해, 글은 intent 값을 읽고 실제로 눌린 버튼만 대기 표시를 하도록 useFormStatusForIntent() 훅을 만듭니다. 단, 버튼별 formAction을 지정한 경우에는 이 방식이 동작하지 않습니다. 텍스트 필드에서 Enter를 누르면 마크업상 첫 번째 버튼이 기본 제출 버튼이 되므로, 기본 동작으로 실행해도 괜찮은 작업을 먼저 배치해야 합니다.
필드 잠금과 상태 알림
요청이 진행되는 동안 입력을 막으려면 각 입력 요소에 disabled를 따로 지정하는 대신 <fieldset disabled={pending}>을 쓸 수 있습니다. 단, 필드셋이 비활성화되면 포커스된 입력 요소도 포커스를 잃습니다. 편집을 막는 일이 포커스 유지보다 우선일 때 사용하라고 설명합니다. 기본 테두리를 지우는 인라인 스타일은 CSP가 인라인 스타일을 차단하면 서버 렌더링 HTML에서 적용되지 않을 수 있습니다. 그런 환경에서는 인라인 초기화를 끄고 CSS 클래스로 스타일을 지정합니다.
aria-busy만으로는 스크린 리더가 대기 상태를 소리 내 알리지 않습니다. 글에서는 role="status", aria-live="polite", aria-atomic="true"를 지정한 FormStatusText를 추가해 대기·유휴 메시지를 전달합니다.
폼 배치 검사와 한계
SubmitButton은 폼 바깥에서 렌더링할 때 개발 환경 콘솔에 오류를 출력하는 검사를 갖추고 있습니다. 글쓴이는 PendingFieldset과 FormStatusText에도 같은 검사를 추가했습니다. 두 구성 요소도 폼 안에 있어야 하지만, 잘못 배치하면 필드 잠금이나 상태 메시지가 작동하지 않아도 경고가 없었기 때문입니다. 검사는 DOM에서 폼 내부 여부를 확인합니다. React 트리에서는 폼 아래에 있지만 portal로 DOM 바깥에 렌더링하는 경우에는 오탐이 생길 수 있어 unsafeSkipFormCheck로 검사를 건너뜁니다.
전체 구현은 react와 react-dom 외 의존성이 없는 단일 파일입니다. 알려진 한계로는 버튼별 formAction 미지원, form.requestSubmit() 호출 시 클릭 차단 로직 우회, React가 다시 렌더링하기 전 같은 작업 안에서 발생한 동기 클릭 두 번의 통과가 있습니다. 글쓴이는 Chromium과 Firefox에서만 테스트했으며 Safari와 스크린 리더는 아직 확인하지 않았습니다.
dev.to 반응
- @elijahbrown — aria-disabled를 pending 상태에 쓰는 방식이 좋은 지적입니다. 연락처 폼에서 관련해 조심할 점이 하나 있습니다. 형식이 올바른 이메일 주소라도 해당 도메인에 MX 레코드가 없으면 제출이 성공한 것처럼 보이다가 확인 메일이 반송될 수 있습니다.
- @webdeveloperhyper — 조용히 실패하는 버그는 알아차리기 어렵습니다. 모든 버그가 알아차리기 쉽고 찾기도 쉬웠으면 좋겠습니다. 버그 잘 잡으셨네요! 😄
원문: dev.to / 번역·요약: Trawling