useFormStatus

김동현·2026년 3월 17일

useFormStatus

소개

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)시키고 있어요.

아래에서 더 많은 예제를 확인하세요.

매개변수(Parameters)

useFormStatus는 어떤 매개변수도 받지 않아요.

반환값(Returns)

다음과 같은 프로퍼티들을 가진 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.actionnull이 돼요.

주의사항(Caveats)

  • useFormStatus Hook은 반드시 <form> 안에서 렌더링되는 컴포넌트에서 호출해야 해요.
  • useFormStatus는 부모 <form>에 대한 상태 정보만 반환해요. 같은 컴포넌트나 자식 컴포넌트에서 렌더링된 다른 <form>의 상태 정보는 반환하지 않아요.

💡 부연 설명: 쉽게 말해서, useFormStatus를 호출하는 컴포넌트가 <form> 태그의 자식으로 들어가 있어야 한다는 뜻이에요. 같은 컴포넌트 안에서 <form>을 렌더링하면서 동시에 useFormStatus를 호출하면 작동하지 않아요. 반드시 별도의 자식 컴포넌트로 분리해서 사용해야 해요!


사용법

폼 제출 중에 대기(pending) 상태 표시하기

폼이 제출되는 동안 대기 상태를 표시하려면, <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>의 상태 정보를 반환하지 않아요.

useFormStatus Hook은 부모 <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() 메서드를 사용해서 특정 필드의 값을 가져올 수 있어요. 폼이 제출 중이 아닐 때는 datanull이므로, 반드시 data가 존재하는지 확인한 후에 사용해야 해요. 위 예제에서 data?.get("username")처럼 옵셔널 체이닝(?.)을 쓴 것도 바로 이 때문이에요.


문제 해결(Troubleshooting)

status.pending이 절대 true가 되지 않아요

useFormStatus는 부모 <form>에 대한 상태 정보만 반환해요.

useFormStatus를 호출하는 컴포넌트가 <form> 안에 중첩되어 있지 않으면, status.pending은 항상 false를 반환할 거예요. useFormStatus<form> 엘리먼트의 자식인 컴포넌트에서 호출되고 있는지 확인하세요.

useFormStatus는 같은 컴포넌트에서 렌더링된 <form>의 상태를 추적하지 않아요. 더 자세한 내용은 함정(Pitfall) 섹션을 참고하세요.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글