번역 자료 / / 2025. 5. 27. 09:44

[react-dom 번역] <form>

출처: https://react.dev/reference/react-dom/components/form


개요

내장 브라우저 <form> 컴포넌트는 정보를 제출하기 위한 인터랙티브 컨트롤을 생성할 수 있게 해줍니다.

<form action={search}>
  <input name="query" />
  <button type="submit">Search</button>
</form>

Reference

<form>

정보 제출을 위한 인터랙티브 컨트롤을 만들려면 내장 <form> 컴포넌트를 렌더링하세요.

<form action={search}>
  <input name="query" />
  <button type="submit">Search</button>
</form>

Props

  • action: URL 또는 함수. URL을 전달하면 HTML form과 동일하게 동작합니다. 함수를 전달하면 폼 제출 시 해당 함수가 호출됩니다(비동기 함수도 가능). 함수에는 제출된 form의 FormData가 인자로 전달됩니다. 각 <button>, <input type="submit">, <input type="image">formAction prop으로 action을 오버라이드할 수 있습니다.

Caveats(주의사항)

  • action/formAction에 함수를 전달하면 HTTP method는 method prop과 무관하게 항상 POST가 됩니다.

사용법

클라이언트에서 폼 제출 처리

폼의 action prop에 함수를 전달하면, 폼 제출 시 해당 함수가 실행됩니다. 함수의 인자로 formData가 전달되어, 제출된 데이터를 읽을 수 있습니다. (기존 HTML form의 action은 URL만 허용)

export default function Search() {
  function search(formData) {
    const query = formData.get("query");
    alert(`You searched for '${query}'`);
  }
  return (
    <form action={search}>
      <input name="query" />
      <button type="submit">Search</button>
    </form>
  );
}
  • action 함수가 성공하면, form 내의 uncontrolled 필드는 자동으로 reset됩니다.

Server Function으로 폼 제출 처리

Server Function('use server'가 선언된 함수)을 action에 전달하면, 자바스크립트가 비활성화된 환경이나 코드가 로드되기 전에도 폼 제출이 가능합니다.

import { updateCart } from './lib.js';

function AddToCart({productId}) {
  async function addToCart(formData) {
    'use server'
    const productId = formData.get('productId')
    await updateCart(productId)
  }
  return (
    <form action={addToCart}>
      <input type="hidden" name="productId" value={productId} />
      <button type="submit">Add to Cart</button>
    </form>
  );
}
  • hidden 필드로 추가 데이터를 전달할 수 있습니다.
  • bind 메서드로 추가 인자를 전달할 수도 있습니다.
function AddToCart({productId}) {
  async function addToCart(productId, formData) {
    "use server";
    await updateCart(productId)
  }
  const addProductToCart = addToCart.bind(null, productId);
  return (
    <form action={addProductToCart}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}
  • Server Component에서 렌더링되고, Server Function이 action에 전달되면 progressive enhancement가 적용됩니다.

폼 제출 중 상태 표시 (useFormStatus)

폼 제출 중에는 useFormStatus Hook을 사용해 pending 상태를 표시할 수 있습니다.

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>
  );
}

Optimistic UI (useOptimistic)

useOptimistic Hook을 사용하면, 네트워크 요청이 끝나기 전에 UI를 미리 업데이트할 수 있습니다.

import { useOptimistic, useState, useRef } from "react";
import { deliverMessage } from "./actions.js";

function Thread({ messages, sendMessage }) {
  const formRef = useRef();
  async function formAction(formData) {
    addOptimisticMessage(formData.get("message"));
    formRef.current.reset();
    await sendMessage(formData);
  }
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true
      }
    ]
  );

  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

폼 제출 에러 처리 (Error Boundary)

action 함수에서 에러가 발생하면, 폼을 Error Boundary로 감싸 fallback UI를 표시할 수 있습니다.

import { ErrorBoundary } from "react-error-boundary";

export default function Search() {
  function search() {
    throw new Error("search error");
  }
  return (
    <ErrorBoundary fallback={<p>There was an error while submitting the form</p>}>
      <form action={search}>
        <input name="query" />
        <button type="submit">Search</button>
      </form>
    </ErrorBoundary>
  );
}

자바스크립트 없이 폼 제출 에러 표시 (useActionState)

Server Component에서 useActionState와 Server Function을 사용하면, JS 번들 로드 전에도 에러 메시지를 표시할 수 있습니다.

import { useActionState } from "react";
import { signUpNewUser } from "./api";

export default function Page() {
  async function signup(prevState, formData) {
    "use server";
    const email = formData.get("email");
    try {
      await signUpNewUser(email);
      alert(`Added "${email}"`);
    } catch (err) {
      return err.toString();
    }
  }
  const [message, signupAction] = useActionState(signup, null);
  return (
    <>
      <h1>Signup for my newsletter</h1>
      <p>Signup with the same email twice to see an error</p>
      <form action={signupAction} id="signup-form">
        <label htmlFor="email">Email: </label>
        <input name="email" id="email" placeholder="react@example.com" />
        <button>Sign up</button>
        {!!message && <p>{message}</p>}
      </form>
    </>
  );
}

여러 제출 타입 처리 (formAction)

form 내 여러 버튼에 각각 다른 action을 지정할 수 있습니다.

export default function Search() {
  function publish(formData) {
    const content = formData.get("content");
    const button = formData.get("button");
    alert(`'${content}' was published with the '${button}' button`);
  }
  function save(formData) {
    const content = formData.get("content");
    alert(`Your draft of '${content}' has been saved!`);
  }
  return (
    <form action={publish}>
      <textarea name="content" rows={4} cols={40} />
      <br />
      <button type="submit" name="button" value="submit">Publish</button>
      <button formAction={save}>Save draft</button>
    </form>
  );
}

참고 및 주의사항

  • action에 함수를 전달하면 method는 항상 POST입니다.
  • form 내 여러 버튼에 formAction을 지정해 다양한 제출 동작을 구현할 수 있습니다.
  • Server Function을 action에 전달하면 progressive enhancement가 적용됩니다.
  • 보안상, 입력 데이터 검증 및 에러 처리를 반드시 구현하세요.

출처: https://react.dev/reference/react-dom/components/form

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유