무엇
useFormStatus는 상위 form의 제출 상태를 읽는 훅이다. 지금 제출 중인지(pending), 무슨 데이터를 보내는지(data)를 form 안쪽의 컴포넌트가 직접 알 수 있다.
인자는 없다. 돌려받는 status 객체에는 pending, data, method, action 네 필드가 있다.
import { useFormStatus } from 'react-dom'
const { pending, data, method, action } = useFormStatus()
pending은 상위 form이 제출 중이면 true다. data는 제출 중인 값을 담은 FormData(제출 중이 아니면 null), method는 'get' 또는 'post', action은 form의 action에 넘긴 함수 참조(URI거나 없으면 null)다.
무슨 일이 일어나나
이 훅은 상위 form의 상태만 읽는다. 자기 자신이나 자식이 렌더하는 form은 보지 못한다. 그래서 form을 그리는 컴포넌트에서 부르면 pending은 절대 true가 되지 않는다.
// 잘못된 위치 — pending이 늘 false
function Form() {
const { pending } = useFormStatus()
return <form action={submit} />
}
제대로 쓰려면 form보다 한 단계 안쪽 컴포넌트에서 불러야 한다. 그 컴포넌트가 자기 위에서 가장 가까운 form을 찾아 상태를 받는다. Context를 직접 잇지 않고도 form과 제출 버튼 사이의 상태를 연결하는 셈이다.
사용법
제출 버튼을 별도 컴포넌트로 빼서 그 안에서 부른다. 제출 중에는 버튼을 잠그고 문구를 바꾼다.
import { useFormStatus } from 'react-dom'
// form 안쪽에 놓여야 pending이 동작한다
export function Submit() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? '보내는 중…' : '보내기'}
</button>
)
}
버튼을 감싸는 form은 바깥에서 그린다. Submit이 그 form을 상위로 보고 상태를 읽는다.
function Form({ action }: { action: (data: FormData) => void }) {
return (
<form action={action}>
<Submit />
</form>
)
}
실무 예시
제출 중에 버튼을 잠그는 것에 더해, 방금 무엇을 보냈는지 data로 미리 비추는 패턴이다. data는 FormData라 get으로 필드를 꺼낸다.
import { useFormStatus } from 'react-dom'
function SubmitRow() {
const { pending, data } = useFormStatus()
return (
<>
<button type="submit" disabled={pending}>
{pending ? '요청 중…' : '변경'}
</button>
{data && <p>보내는 이름: {String(data.get('username'))}</p>}
</>
)
}
export function UsernameForm({ action }: { action: (data: FormData) => void }) {
return (
<form action={action}>
<input name="username" />
<SubmitRow />
</form>
)
}
왜 중요한가
form action과 함께 React 19에서 들어온 훅으로, 제출 중 상태를 별도 state 없이 버튼 쪽에서 바로 다룰 수 있게 해 준다. 로딩 표시를 위해 form 상단에서 관리하던 isSubmitting 같은 상태를 덜어 준다. 서버 액션이나 form action 기반 폼을 쓸 때 제출 버튼의 표준 짝이 되고, 그 흐름을 쓰지 않는다면 등장할 일은 없다.
Reference