useFormStatus는 마지막 폼 제출의 상태 정보를 알려주는 Hook이에요.
const { pending, data, method, action } = useFormStatus();
useFormStatus()useFormStatus Hook은 마지막 폼 제출에 대한 상태 정보를 제공해줘요.
// src/App.js
import { useFormStatus } from "react-dom";
import action from './actions';
function Submit() {
const status = useFormStatus();
return <button disabled={status.pending}>Submit</button>
}
export default function App() {
return (
<form action={action}>
<Submit />
</form>
);
}
상태 정보를 얻으려면, Submit 컴포넌트가 반드시 <form> 안에서 렌더링되어야 해요. 이 Hook은 pending 프로퍼티 같은 정보를 반환하는데, 이걸 통해 폼이 현재 제출 중인지 알 수 있어요.
위 예제에서 Submit 컴포넌트는 이 정보를 활용해서 폼이 제출되는 동안 <button>을 비활성화(disable)시키고 있어요.
useFormStatus는 어떤 매개변수도 받지 않아요.
다음과 같은 프로퍼티들을 가진 status 객체를 반환해요:
pending: 불리언(boolean) 값이에요. true이면 부모 <form>이 현재 제출 대기 중이라는 뜻이고, 그렇지 않으면 false예요.
data: FormData 인터페이스를 구현한 객체로, 부모 <form>이 제출하고 있는 데이터를 담고 있어요. 활성화된 제출이 없거나 부모 <form>이 없으면 null이 돼요.
method: 'get' 또는 'post' 중 하나의 문자열 값이에요. 부모 <form>이 GET 또는 POST HTTP 메서드 중 어떤 걸로 제출하고 있는지를 나타내요. 기본적으로 <form>은 GET 메서드를 사용하고, method 프로퍼티로 지정할 수 있어요.
action: 부모 <form>의 action prop에 전달된 함수에 대한 참조예요. 부모 <form>이 없으면 이 프로퍼티는 null이에요. action prop에 URI 값이 제공되었거나 action prop이 지정되지 않은 경우에는 status.action이 null이 돼요.
useFormStatus Hook은 반드시 <form> 안에서 렌더링되는 컴포넌트에서 호출해야 해요.useFormStatus는 부모 <form>에 대한 상태 정보만 반환해요. 같은 컴포넌트나 자식 컴포넌트에서 렌더링된 다른 <form>의 상태 정보는 반환하지 않아요.💡 부연 설명: 쉽게 말해서,
useFormStatus를 호출하는 컴포넌트가<form>태그의 자식으로 들어가 있어야 한다는 뜻이에요. 같은 컴포넌트 안에서<form>을 렌더링하면서 동시에useFormStatus를 호출하면 작동하지 않아요. 반드시 별도의 자식 컴포넌트로 분리해서 사용해야 해요!
폼이 제출되는 동안 대기 상태를 표시하려면, <form> 안에서 렌더링되는 컴포넌트에서 useFormStatus Hook을 호출하고 반환된 pending 프로퍼티를 읽으면 돼요.
여기서는 pending 프로퍼티를 사용해서 폼이 제출 중임을 나타내고 있어요.
// src/App.js
import { useFormStatus } from "react-dom";
import { submitForm } from "./actions.js";
function Submit() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Submitting..." : "Submit"}
</button>
);
}
function Form({ action }) {
return (
<form action={action}>
<Submit />
</form>
);
}
export default function App() {
return <Form action={submitForm} />;
}
// src/actions.js
export async function submitForm(query) {
await new Promise((res) => setTimeout(res, 1000));
}
⚠️ 함정(Pitfall)
useFormStatus는 같은 컴포넌트에서 렌더링된<form>의 상태 정보를 반환하지 않아요.
useFormStatusHook은 부모<form>의 상태 정보만 반환해요. 같은 컴포넌트에서 렌더링된<form>이나 자식 컴포넌트의<form>에 대해서는 상태 정보를 반환하지 않아요.function Form() { // 🚩 `pending`은 절대 true가 되지 않아요 // useFormStatus는 이 컴포넌트에서 렌더링된 form을 추적하지 않아요 const { pending } = useFormStatus(); return <form action={submit}></form>; }대신,
<form>안에 위치한 컴포넌트 내부에서useFormStatus를 호출하세요.function Submit() { // ✅ `pending`은 Submit 컴포넌트를 감싸는 form으로부터 파생돼요 const { pending } = useFormStatus(); return <button disabled={pending}>...</button>; } function Form() { // 이것이 `useFormStatus`가 추적하는 <form>이에요 return ( <form action={submit}> <Submit /> </form> ); }💡 부연 설명: 이건 정말 흔히 하는 실수 중 하나예요.
useFormStatus는 자신이 호출된 컴포넌트의 부모에 있는<form>을 바라보는 거지, 같은 컴포넌트에 있는<form>을 바라보는 게 아니에요. 그래서 항상<form>안에 들어갈 별도의 컴포넌트를 만들고, 그 컴포넌트 안에서useFormStatus를 호출하는 패턴으로 사용해야 해요.
useFormStatus에서 반환된 상태 정보의 data 프로퍼티를 사용하면 사용자가 제출하고 있는 데이터를 표시할 수 있어요.
여기에는 사용자가 사용자 이름(username)을 요청할 수 있는 폼이 있어요. useFormStatus를 사용해서 사용자가 어떤 사용자 이름을 요청했는지 확인하는 임시 상태 메시지를 표시할 수 있어요.
// src/UsernameForm.js
import {useState, useMemo, useRef} from 'react';
import {useFormStatus} from 'react-dom';
export default function UsernameForm() {
const {pending, data} = useFormStatus();
return (
<div>
<h3>Request a Username: </h3>
<input type="text" name="username" disabled={pending}/>
<button type="submit" disabled={pending}>
Submit
</button>
<br />
<p>{data ? `Requesting ${data?.get("username")}...`: ''}</p>
</div>
);
}
// src/App.js
import UsernameForm from './UsernameForm';
import { submitForm } from "./actions.js";
import {useRef} from 'react';
export default function App() {
const ref = useRef(null);
return (
<form ref={ref} action={async (formData) => {
await submitForm(formData);
ref.current.reset();
}}>
<UsernameForm />
</form>
);
}
// src/actions.js
export async function submitForm(query) {
await new Promise((res) => setTimeout(res, 2000));
}
p {
height: 14px;
padding: 0;
margin: 2px 0 0 0 ;
font-size: 14px
}
button {
margin-left: 2px;
}
💡 부연 설명:
data프로퍼티는FormData객체이기 때문에,data.get("username")처럼get()메서드를 사용해서 특정 필드의 값을 가져올 수 있어요. 폼이 제출 중이 아닐 때는data가null이므로, 반드시data가 존재하는지 확인한 후에 사용해야 해요. 위 예제에서data?.get("username")처럼 옵셔널 체이닝(?.)을 쓴 것도 바로 이 때문이에요.
status.pending이 절대 true가 되지 않아요useFormStatus는 부모 <form>에 대한 상태 정보만 반환해요.
useFormStatus를 호출하는 컴포넌트가 <form> 안에 중첩되어 있지 않으면, status.pending은 항상 false를 반환할 거예요. useFormStatus가 <form> 엘리먼트의 자식인 컴포넌트에서 호출되고 있는지 확인하세요.
useFormStatus는 같은 컴포넌트에서 렌더링된 <form>의 상태를 추적하지 않아요. 더 자세한 내용은 함정(Pitfall) 섹션을 참고하세요.