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가 다음 과정을 자동으로 수행함.
- 사용자가 form을 제출(submit)
→ React가 기본 브라우저 동작을 막고 (내부에서preventDefault()처리). - React가 form의 모든 입력값을 모아
FormData객체를 생성 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용 함수
'TIL*' 카테고리의 다른 글
| Vite를 사용하는 이유, Vite Public 디렉토리 선택 기준 (0) | 2025.11.17 |
|---|---|
| VSCode, Cursor - TypeScript 경로 별칭(@) 자동 import가 안 될 때 (0) | 2025.10.30 |
| 브라우저 엔진, 자바스크립트 엔진 (0) | 2025.10.22 |
| 모노레포 프로젝트에 Turborepo 도입하여 빌드 최적화하기 (0) | 2025.10.13 |
| [작성중] 로그인 구현 방법 정리 (0) | 2025.09.22 |