728x90
728x90
폼(Form) 처리 방법 (React 19)
들어가며
- 예제 코드와 함께 React 19에서 업데이트 된 폼(Form) 처리 방법에 대하여 간단하게 정리해본다.

사용 방법
- 아래의 코드는 React 19에서 새로 추가된 Form 관련 기능들을 적용한 코드이다.
Signup.jsx
import { useActionState } from 'react'; import { isEmail, isNotEmpty, isEqualToOtherValue, hasMinLength, } from '../util/validation'; export default function Signup() { // React 19 이상에서는 폼 제출 시, formData 객체가 생성되고 함수의 인자로 가져와 특정 필드의 입력값을 가져올 수 있다. // 또한, 브라우저의 기본 동작을 자동으로 방지하기 때문에 event.preventDefault() 코드를 따로 작성해주지 않아도 된다. // 그리고 폼 제출 시, 자동으로 필드의 값들이 초기화된다. function singupAction(prevFormState, formData) { const email = formData.get('email'); const password = formData.get('password'); const confirmPassword = formData.get('confirm-password'); const firstName = formData.get('first-name'); const lastName = formData.get('last-name'); const role = formData.get('role'); const terms = formData.get('terms'); const acquisitionChannel = formData.getAll('acquisition'); // 동일한 name이 여러개 있을 경우 // 유효성 검증 let errors = []; if (!isEmail(email)) { errors.push('Invalid email address'); } if (!isNotEmpty(password) || !hasMinLength(password, 6)) { errors.push('You must provide a password with at least six characters.'); } if (!isEqualToOtherValue(password, confirmPassword)) { errors.push('Passwords do not match.'); } if (!isNotEmpty(firstName) || !isNotEmpty(lastName)) { errors.push('Please provide both your first and last name.'); } if (!isNotEmpty(role)) { errors.push('Please select a role.'); } if (!terms) { errors.push('You must agree to the terms and conditions.'); } if (acquisitionChannel.length === 0) { errors.push('Please select at least one acquisition channel.'); } // 에러 표시 if (errors.length > 0) { return { // 에러 문구가 담긴 배열 errors, // 입력값 enteredValues: { email, password, confirmPassword, firstName, lastName, role, acquisitionChannel, terms, }, }; } return { errors: null }; } // useActionState 훅 사용 const [formState, formAction] = useActionState(singupAction, { errors: null, }); return ( <form action={formAction}> {/* React 19 이상에서는 onSubmit이 아닌 action을 통해 폼 제출 이벤트 처리 함수를 전달한다. */} <h2>Welcome on board!</h2> <p>We just need a little bit of data from you to get you started 🚀</p> <div className='control'> <label htmlFor='email'>Email</label> <input id='email' type='email' name='email' defaultValue={formState.enteredValues?.email} /> </div> <div className='control-row'> <div className='control'> <label htmlFor='password'>Password</label> <input id='password' type='password' name='password' defaultValue={formState.enteredValues?.password} /> </div> <div className='control'> <label htmlFor='confirm-password'>Confirm Password</label> <input id='confirm-password' type='password' name='confirm-password' defaultValue={formState.enteredValues?.confirmPassword} /> </div> </div> <hr /> <div className='control-row'> <div className='control'> <label htmlFor='first-name'>First Name</label> <input type='text' id='first-name' name='first-name' defaultValue={formState.enteredValues?.firstName} /> </div> <div className='control'> <label htmlFor='last-name'>Last Name</label> <input type='text' id='last-name' name='last-name' defaultValue={formState.enteredValues?.lastName} /> </div> </div> <div className='control'> <label htmlFor='phone'>What best describes your role?</label> <select id='role' name='role' defaultValue={formState.enteredValues?.role} > <option value='student'>Student</option> <option value='teacher'>Teacher</option> <option value='employee'>Employee</option> <option value='founder'>Founder</option> <option value='other'>Other</option> </select> </div> <fieldset> <legend>How did you find us?</legend> <div className='control'> <input type='checkbox' id='google' name='acquisition' value='google' defaultChecked={formState.enteredValues?.acquisitionChannel.includes( 'google' )} /> <label htmlFor='google'>Google</label> </div> <div className='control'> <input type='checkbox' id='friend' name='acquisition' value='friend' defaultChecked={formState.enteredValues?.acquisitionChannel.includes( 'friend' )} /> <label htmlFor='friend'>Referred by friend</label> </div> <div className='control'> <input type='checkbox' id='other' name='acquisition' value='other' defaultChecked={formState.enteredValues?.acquisitionChannel.includes( 'other' )} /> <label htmlFor='other'>Other</label> </div> </fieldset> <div className='control'> <label htmlFor='terms-and-conditions'> <input type='checkbox' id='terms-and-conditions' name='terms' defaultChecked={formState.enteredValues?.terms} /> I agree to the terms and conditions </label> </div> {/* 오류 메시지 표시 */} {formState.errors && ( <ul className='errors'> {formState.errors.map((error) => ( <li key={error}>{error}</li> ))} </ul> )} <p className='form-actions'> <button type='reset' className='button button-flat'> Reset </button> <button className='button'>Sign up</button> </p> </form> ); }
React 19에서의 폼 동작
- React 19에서는 폼 제출 시,
formData
객체가 생성되고 폼 제출 이벤트 처리 함수의 인자로 가져와 특정 필드의 입력값을 가져올 수 있다. - 또한, 브라우저의 기본 동작을 자동으로 방지하기 때문에
event.preventDefault()
코드를 폼 제출 이벤트 처리 함수 안에 따로 작성해주지 않아도 된다. - 그리고 폼 제출시 필드의 입력값이 자동으로 사라진다.
function singupAction(prevFormState, formData) { const email = formData.get('email'); const password = formData.get('password'); const confirmPassword = formData.get('confirm-password'); const firstName = formData.get('first-name'); const lastName = formData.get('last-name'); const role = formData.get('role'); const terms = formData.get('terms'); const acquisitionChannel = formData.getAll('acquisition'); // 동일한 name이 여러개 있을 경우 // 유효성 검증 let errors = []; if (!isEmail(email)) { errors.push('Invalid email address'); } if (!isNotEmpty(password) || !hasMinLength(password, 6)) { errors.push('You must provide a password with at least six characters.'); } if (!isEqualToOtherValue(password, confirmPassword)) { errors.push('Passwords do not match.'); } if (!isNotEmpty(firstName) || !isNotEmpty(lastName)) { errors.push('Please provide both your first and last name.'); } if (!isNotEmpty(role)) { errors.push('Please select a role.'); } if (!terms) { errors.push('You must agree to the terms and conditions.'); } if (acquisitionChannel.length === 0) { errors.push('Please select at least one acquisition channel.'); } // 에러 표시 if (errors.length > 0) { return { // 에러 문구가 담긴 배열 errors, // 입력값 enteredValues: { email, password, confirmPassword, firstName, lastName, role, acquisitionChannel, terms, }, }; } return { errors: null }; }
- 폼 제출 이벤트 처리 함수(
signupAction
)에formData
인자를 통해 폼 요소의 필드 안의 값들에 접근하여 가져올 수 있다. <input>
요소에defaultValue
또는defaultChecked
속성을 추가하여 유효성 검증(Validation Check) 실패 시, 입력했던 값들이 사라지지 않도록 할 수 있다.- 이때 폼 제출 이벤트 처리 함수 안에 해당 기본값들을 반환할 수 있도록 해주어야 한다.
- 유효성 검증에 통과하여 성공적으로 폼 제출 이벤트 처리 시, 폼 요소의 필드 값들의 내용은 모두 사라진다.
useActionState
훅을 이용하여 폼의 필드에 입력된 값들의 상태 관리(formState
)과 폼 제출 시 처리할 액션(formAction
)을 관리할 수 있다.- 이때, 폼 제출 이벤트 처리 함수의 첫 번째 인자로
prevFormState
매개변수를 반드시 추가해주어야 한다.- 그렇지 않을 경우 오류가 발생한다. ⚠️
- 이때, 폼 제출 이벤트 처리 함수의 첫 번째 인자로
import { useActionState } from 'react'; // ... function singupAction(prevFormState, formData) { // prevFormState 매개변수 추가 (첫 번째) // .. } // ... const [formState, formAction] = useActionState(singupAction, { errors: null, });
use
훅과 useFormStatus
훅과 함께 사용하기
NewOpinion.jsx
- React 19에서 새로 추가된
use
훅과 함께 폼 요소 이벤트를 처리할 수 있다.use
훅은Suspense
와 함께 작동하는Promise
객체를 받아 비동기적으로 데이터를 처리해준다.
import { useActionState, use } from 'react'; import { OpinionsContext } from '../store/opinions-context'; import Submit from './Submit'; export function NewOpinion() { const { addOpinion } = use(OpinionsContext); // 폼 액션 처리 함수 async function shareOpinionAction(prevState, formData) { const title = formData.get('title'); const body = formData.get('body'); const userName = formData.get('userName'); // 유효성 검증 let errors = []; if (title.trim().length < 5) { errors.push('Title must be at least five characters long.'); } if (body.trim().length < 10 || body.trim().length > 300) { errors.push('Opinion must be between 10 and 300 characters long.'); } if (!userName.trim()) { errors.push('Please provide your name.'); } if (errors.length > 0) { return { errors, enteredValues: { title, body, userName, }, }; } // 백엔드로 전송 await addOpinion({ title, body, userName }); // 추가 return { errors: null }; } const [formState, formAction] = useActionState(shareOpinionAction, { errors: null, }); return ( <div id='new-opinion'> <h2>Share your opinion!</h2> <form action={formAction}> <div className='control-row'> <p className='control'> <label htmlFor='userName'>Your Name</label> <input type='text' id='userName' name='userName' defaultValue={formState.enteredValues?.userName} /> </p> <p className='control'> <label htmlFor='title'>Title</label> <input type='text' id='title' name='title' defaultValue={formState.enteredValues?.title} /> </p> </div> <p className='control'> <label htmlFor='body'>Your Opinion</label> <textarea id='body' name='body' rows={5} defaultValue={formState.enteredValues?.body} ></textarea> </p> {/* 에러 표시 */} {formState.errors && ( <ul className='errors'> {formState.errors.map((error) => ( <li key={error}>{error}</li> ))} </ul> )} {/* 제출 버튼 */} <Submit /> </form> </div> ); }
Submit.jsx
useFormStatus
훅은react-dom
에서 제공되는 훅으로, 폼 상태를 관리할 때 사용되는 훅이다.- 이 훅은 폼이 포함된 컴포넌트에서 직접 사용할 수 없으며, 반드시 폼 내부에 중첩된 컴포넌트에서 호출해야 한다.
import { useFormStatus } from 'react-dom'; export default function Submit() { const { pending } = useFormStatus(); return ( <p className='actions'> <button type='submit' disabled={pending}> {pending ? 'Submitting...' : 'Submit'} </button> </p> ); }
마치며
- React 19에서 새롭게 추가된 폼 관련 기능들을 적용한 코드를 통해 빠르게 정리해 보았다.
- 보다 더 자세하고 심화적인 내용(
useOptimistic
훅을 이용한 낙관적 업데이트 등)은 차후에 다시 정리해보려고 한다.
참고 사이트
useActionState – React
The library for web and native user interfaces
ko.react.dev
useFormStatus – React
The library for web and native user interfaces
ko.react.dev
React v19 – React
The library for web and native user interfaces
ko.react.dev
728x90
728x90
'Programming > React' 카테고리의 다른 글
[React.js] React Hook Form 라이브러리 (0) | 2024.11.23 |
---|---|
[React.js] 객체 표기법(Object Notation) 방식과 빌더 콜백 표기법(Builder Callback Notation) 방식 (0) | 2024.11.13 |
[React.js] .js 파일에서 Uncaught SyntaxError: Unexpected token '<' 오류 발생할 때 해결 방법 (Vite) (0) | 2024.11.13 |
[React.js] <Link> 컴포넌트와 <NavLink> 컴포넌트 비교 (React Router) (0) | 2024.11.13 |
[React.js] 리덕스(Redux), 리덕스 툴킷(Redux Toolkit) (0) | 2024.11.11 |
[React.js] 모든 웹 브라우저에서 공통된 HTML 요소 스타일이 보여지도록 설정하는 방법 (normalize.css) (0) | 2024.11.07 |
[React.js] Recharts 라이브러리 (1) | 2024.11.06 |
[React.ts] PropsWithChildren (0) | 2024.11.05 |