form submit 동작 방식 정리(HTML, React18, React19)

2025. 10. 28. 17:45

React 19부터는 <form> 태그의 action 속성에 함수를 직접 전달할 수 있게 되면서, 폼 제출 방식이 변경됐다.

HTML, React 18, React 19 세 가지 방식의 동작 차이를 비교 정리한 내용이다.

1. 기본 HTML form 태그 action의 동작 방식:

표준 HTML에서 action 속성은 URL 문자열을 받음. 따라서 submit 시 브라우저가 그 URL로 요청을 보냄

1) 전통적 방식 (서버로 바로 제출)

<form action="/submit" method="post">
  <input name="name" />
  <button type="submit">전송</button>
</form>

브라우저가 /submit로 네비게이션(페이지 이동)하며 데이터를 제출

2) 자바스크립트로 가로채서 함수에 넘기기 (AJAX)

<form id="myForm">
  <input name="name" />
  <button type="submit">전송</button>
</form>
<script>
  function handleSubmit(formData) {
    // 원하는 로직 수행 (ex. fetch로 비동기 제출)
    return fetch('/submit', { method: 'POST', body: formData });
  }

  const form = document.getElementById('myForm');

  form.addEventListener('submit', (e) => {
    e.preventDefault(); // 기본 제출 막기
    const fd = new FormData(e.currentTarget);
    handleSubmit(fd);  // 여기서 함수에 FormData를 직접 전달
  });
</script>

+)

  • 만약 버튼별 목적지를 바꾸고 싶다면, <button formAction="/other">처럼 버튼의 formAction을 사용할 수 있음(표준 HTML).
  • 페이지 이동 없이 제출하고 싶다면 반드시 preventDefault()로 기본 동작을 막고 FormData를 직접 만들어 사용해야 함

2. React 18버전 이하:

  • onSubmit 이벤트 핸들러 사용
  • 수동으로 preventDefault() 호출
  • 수동으로 FormData 생성
// React 18 이하 - 일반적인 방식
<form onSubmit={handleSubmit}>
  <input name="name" />
  <button type="submit">전송</button>
</form>

function handleSubmit(e) {
  e.preventDefault(); // 기본 동작 막기
  const formData = new FormData(e.currentTarget);
  // fetch나 axios로 직접 제출
}

3. React 19버전:

리액트 공식 문서의 action 항목을 보면 19버전부터 도입된 새로운 Form Action 시스템에 대해 설명하고 있다.

<form>은 모든 공통 엘리먼트 Props를 지원합니다.

action: URL 또는 함수를 받습니다.
- URL이 전달되면: form은 HTML form 컴포넌트처럼 동작합니다.
- 함수가 전달되면: 해당 함수는 Transition 컨텍스트 안에서 실행되며, Action Prop 패턴에 따라 폼 제출을 처리합니다.
action에 전달된 함수는 async가 가능하고, 제출된 form의 form data를 포함하는 단일 인자와 함께 호출됩니다.
action prop은 <button>, <input type="submit">, 또는 <input type="image"> 컴포넌트의 formAction 속성으로 오버라이드될 수 있습니다.

주의 사항
함수를 action이나 formAction에 전달하면, HTTP 메서드는 method 프로퍼티의 값과 관계없이 POST로 처리됩니다.

React 19버전부터는 form action 속성에 함수를 전달할 수 있게 되었고, formData 객체를 자동으로 인자로 넣어 준다.

<form action={handleSubmit}>
  {/* input fields */}
</form>

React는 위와 같이 <form action={fn}> 형태를 만나면, 해당 함수(fn)을 Form Action으로 인식하는데, 이 함수가 서버에서 선언되면 서버 액션(Server Action), 클라이언트 컴포넌트 안에서 선언되면 클라이언트 폼 핸들러(Client Form Action) 로 동작함.

그래서 React가 다음 과정을 자동으로 수행함.

  1. 사용자가 form을 제출(submit)
    → React가 기본 브라우저 동작을 막고 (내부에서 preventDefault() 처리).
  2. React가 form의 모든 입력값을 모아 FormData 객체를 생성
  3. action에 전달된 함수(handleSubmit)를 호출하면서 자동으로 FormData를 인자로 넣어줌.

즉, React 내부에서:

form.addEventListener('submit', (e) => {
  e.preventDefault();
  const fd = new FormData(e.currentTarget);
  handleSubmit(fd);
});

→ 그래서 개발자가 따로 e.preventDefault()new FormData()를 할 필요가 없음.

그래서 보통은 handleSubmit과 같은 함수를 다음과 같이 작성해서 사용함.

const handleSubmit = (data) => {
  // data는 FormData 객체
  // 가공 후 axios로 서버 요청
};

+)

...in a Transition following the Action prop pattern. 의 의미:

form action에 함수를 전달하면, 자동으로 Transition으로 래핑한다.

// React 내부 구현
function handleFormSubmit(action, formData) {
  // React가 자동으로 transition context 생성
  const [isPending, startTransition] = createTransition();

  startTransition(async () => {
    await action(formData);
  });

  // isPending 상태는 useFormStatus로 접근 가능
}
  • action에 넘기는 함수는 비동기로 실행될 수 있고, 실행 중에 UI가 반응성을 유지해야 하고 "제출 중" 상태를 추적할 수 있어야 하기 때문에 Transition 으로 래핑하는 것이다.
  • Transition의 역할:
    • 급하지 않은 업데이트(non-urgent update)로 표시
    • UI가 블로킹되지 않고 반응성 유지

++)

formAction 으로 오버라이드한다는 의미:

버튼마다 다른 action을 지정할 수 있다.

async function saveAsDraft(formData) { ... }
async function publish(formData) { ... }

<form action={saveAsDraft}>  {/* 기본 action */}
  <input name="title" />

  <button type="submit">
    임시저장  {/* saveAsDraft 실행 */}
  </button>

  <button type="submit" formAction={publish}>
    발행  {/* publish 실행 (오버라이드) */}
  </button>
</form>

참고로, <input type="image"> 은 submit될 때 클릭한 좌표(x, y)도 함께 전송된다고 함.

+++)

관련하여 19버전에 추가된 useActionState, useFormStatus, 서버 액션 기능 간단 정리

  • useActionState: 폼 제출 상태와 제출 결과를 관리
  • useFormStatus: form의 자식 컴포넌트에서만 사용 가능. 부모 컴포넌트의 form 제출 상태 읽기
  • 서버 액션: 서버에서만 실행되는 form action용 함수