React 19의 새로운 훅과 서버 액션을 통한 폼 처리 재고찰
Ethan Miller
Product Engineer · Leapcell

소개
웹 개발 환경은 React와 같은 프레임워크가 끊임없이 가능한 것의 경계를 넓혀가면서 끊임없이 진화하고 있습니다. 오랫동안 React에서 폼을 관리하는 것은 복잡한 상태 관리, 유효성 검사 라이브러리, 그리고 복잡한 데이터 제출 패턴을 포함하는 혁신의 풍부한 영역이었습니다. 효과적이었지만, 이러한 접근 방식은 때때로 상용구 코드를 도입하고 클라이언트 측 상호 작용과 서버 측 응답 간의 분리를 초래했습니다.
하지만 React 19의 등장으로 새로운 패러다임이 나타나고 있습니다. 강력한 새로운 훅의 도입, 특히 서버 액션의 혁신적인 잠재력과 결합될 때, 폼 처리 경험을 근본적으로 단순화하고 간소화할 것을 약속합니다. 이것은 단순히 사소한 최적화가 아니라, 폼이 서버 측 로직과 통합되는 방식에 대한 총체적인 재평가로, 더 높은 성능, 더욱 강력하고 개발자 친화적인 애플리케이션을 이끌어낼 것입니다. 이 글에서는 이러한 새로운 기능을 심층적으로 살펴보고, 폼 제출 및 데이터 상호 작용을 재정의하기 위해 어떻게 함께 작동하는지 탐구할 것입니다.
React 19의 혁신적인 폼 처리 접근 방식
실제 적용 사례를 탐구하기 전에, React 19에서 이 혁신을 주도하는 핵심 개념에 대한 기초적인 이해를 확립해 봅시다.
핵심 용어
- 서버 액션 (Server Actions): React의 새로운 기능으로, REST 또는 GraphQL과 같은 별도의 API 계층 없이도 클라이언트 측 코드에서 직접 서버 측 함수를 호출할 수 있습니다. 데이터 변경 및 폼 제출을 더욱 직접적이고 효율적인 방식으로 처리할 수 있게 합니다.
useFormStatus: 부모<form>요소의 제출 상태에 대한 정보를 제공하는 React 훅입니다. 폼이 보류 중인지, 제출되었는지, 또는 오류가 있는지 알 수 있어 반응형 UI를 구축하기 쉽게 만듭니다.useFormState: 폼 액션에 명확하게 연결된 상태를 관리할 수 있는 또 다른 강력한 React 훅입니다. 서버 액션의 결과를 컴포넌트의 상태로 직접 다시 가져오는 방법을 제공하여, 오류 메시지나 성공 상태를 포함한 폼 제출 결과의 원활한 처리를 가능하게 합니다.
기존 폼 처리의 문제점
과거 React에서 폼을 처리하는 것은 다음을 포함했습니다:
- 클라이언트 측 상태 관리: 각 입력 필드에
useState를 사용합니다. - 유효성 검사: 종종 외부 라이브러리 또는 클라이언트에서의 사용자 정의 유효성 검사 로직을 사용합니다.
- 데이터 제출: API 엔드포인트로의 비동기
fetch호출입니다. - 로딩 상태:
isLoading플래그의 수동 관리입니다. - 오류 처리:
fetch오류를 잡고 메시지를 표시합니다.
이 다단계 프로세스는 기능적이었지만, 종종 중복된 로직, UI와 실제 서버 작업 간의 단절, 그리고 상당한 양의 상용구 코드를 초래했습니다. 서버 액션과 새로운 폼 훅은 이 복잡성의 많은 부분을 추상화하는 것을 목표로 합니다.
서버 액션이 폼 제출을 간소화하는 방법
서버 액션을 사용하면 서버에 함수를 정의하여 form의 action 속성 또는 버튼의 formAction에서 직접 호출할 수 있습니다. 폼이 제출되면 React는 네트워크 요청을 자동으로 처리하고, 폼 데이터를 직렬화하며, 해당 서버 함수를 호출합니다.
서버 액션의 간단한 예제를 살펴봅시다:
// app/actions.js (또는 서버에서 렌더링되는 컴포넌트 파일에 직접) "use server"; // 이 지시문은 파일/함수를 서버 액션으로 표시합니다. export async function createTodo(formData) { const title = formData.get("title"); // 실제 앱에서는 이를 데이터베이스에 저장할 것입니다. console.log(`Server received new todo: ${title}`); return { message: `Todo "${title}" created successfully!` }; }
이제 이것을 폼과 함께 어떻게 사용할까요?
// components/TodoForm.js import { createTodo } from '../app/actions'; function TodoForm() { return ( <form action={createTodo}> <input type="text" name="title" required /> <button type="submit">Add Todo</button> </form> ); }
이 설정만으로도 React는 폼 제출을 처리하고, 서버에서 createTodo를 호출하며, 성공적인 제출 후 클라이언트에서 데이터를 다시 검증합니다. 이 모든 것이 수동 fetch 호출이나 폼 데이터 자체를 위한 useState 없이 이루어집니다!
useFormStatus로 폼 강화하기
위의 내용이 강력하지만, 제출 중에 UI 피드백을 제공하고 싶다면 어떻게 해야 할까요? 이때 useFormStatus가 빛을 발합니다.
useFormStatus는 form 내부 또는 form 내부에 렌더링되는 컴포넌트에서 사용하도록 설계되었습니다. pending 상태를 제공하며, 폼의 서버 액션이 실행 중일 때 true입니다.
// components/SubmitButton.js import { useFormStatus } from "react-dom"; // 참고: react-dom에 있으며, react에는 없습니다. function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? "Adding..." : "Add Todo"} </button> ); } // components/TodoForm.js (SubmitButton을 사용하도록 업데이트됨) import { createTodo } from '../app/actions'; import SubmitButton from './SubmitButton'; function TodoForm() { return ( <form action={createTodo}> <input type="text" name="title" required /> <SubmitButton /> </form> ); }
이제 폼이 제출될 때, 버튼은 자동으로 비활성화되고 텍스트가 변경되어 사용자에게 즉각적인 피드백을 제공합니다. pending 상태를 위한 로컬 useState 없이 말이죠.
useFormState로 폼 상태 및 피드백 관리하기
서버 액션이 메시지(성공 또는 오류 등)를 반환해야 하고 사용자가 해당 메시지를 표시하고 싶다면 어떻게 해야 할까요? 이것이 useFormState의 역할입니다.
useFormState는 두 개의 인수를 받습니다:
- 서버 액션 함수.
- 초기 상태 값.
배열을 반환하는데, 여기에는 다음이 포함됩니다:
- 현재 상태 (서버 액션의 반환 값).
- 폼의
action속성에 전달하는 새로운 폼 액션.
createTodo 액션을 오류 처리를 포함하도록 개선해 봅시다:
// app/actions.js "use server"; export async function createTodo(prevState, formData) { // prevState는 이제 첫 번째 인수입니다. const title = formData.get("title"); if (!title || title.trim() === "") { return { error: "Todo title cannot be empty." }; } // 데이터베이스 저장 시뮬레이션 await new Promise(resolve => setTimeout(resolve, 500)); // 네트워크 지연 시뮬레이션 if (Math.random() > 0.8) { // 임의의 서버 오류 시뮬레이션 return { error: `Failed to create todo: "${title}". Please try again.` }; } console.log(`Server received new todo: ${title}`); return { message: `Todo "${title}" created successfully!` }; }
이제 TodoForm에서 useFormState와 함께 이것을 통합해 봅시다:
// components/TodoForm.js import { useFormState } from "react-dom"; // react-dom에서 가져옴 import { createTodo } from '../app/actions'; import SubmitButton from './SubmitButton'; const initialState = { message: null, error: null, }; function TodoForm() { const [state, formAction] = useFormState(createTodo, initialState); return ( <form action={formAction}> {/* 여기서 반환된 formAction을 사용합니다. */} <input type="text" name="title" required /> <SubmitButton /> {state.error && <p style={{ color: 'red' }}>{state.error}</p>} {state.message && <p style={{ color: 'green' }}>{state.message}</p>} </form> ); }
이 향상된 예제에서:
useFormState는 서버 액션이 완료된 후createTodo의 반환 값으로 자동으로 업데이트될state를 제공합니다.state.error또는state.message를 렌더링하여 사용자에게 직접 피드백을 제공합니다.useFormState에서 반환된formAction은<form>의action속성에 전달되어 올바른 서버 액션이 호출되고 그 결과가 캡처되도록 합니다.
간단한 폼을 넘어선 응용
이 훅과 서버 액션의 효과는 단순한 할 일 목록을 훨씬 넘어섭니다:
- 복잡한 데이터 입력: 각 단계가 서버에 데이터를 저장하는 다단계 폼을 상상해 보세요. 서버 액션은 각 단계의 제출을 처리하여 실시간 피드백과 유효성 검사를 제공할 수 있습니다.
- 사용자 인증: 로그인 및 등록 폼은 서버 액션을 직접 호출하여 사용자를 인증하고 오류 메시지 또는 성공 상태를
useFormState로 반환할 수 있습니다. - CRUD 작업: 레코드 업데이트, 삭제 및 생성 모두 단순화될 수 있습니다. 각 기능에 대한 별도의 API 엔드포인트와
fetch호출을 정의하는 대신 몇 가지 서버 액션을 정의할 수 있습니다. - 낙관적 UI 업데이트: 이러한 훅에서 직접 처리되지는 않지만, 서버 액션은 낙관적 업데이트를 위한 기반을 마련합니다. 네트워크 호출을 추상화하여 서버 결과를 즉시 추측하고 렌더링한 다음 오류가 발생하면 되돌리기를 더 쉽게 만들기 때문입니다.
이러한 새로운 기능은 클라이언트와 서버 간의 경계를 희미하게 만들어, 더 적은 코드, 더 적은 추상화, 그리고 더 직관적인 개발 경험을 통해 더욱 직접적이고 효율적이며 응집력 있는 방식으로 대화형 애플리케이션을 구축할 수 있게 합니다.
결론
React 19는 혁신적인 useFormStatus 및 useFormState 훅과 강력한 서버 액션을 결합하여 프론트 엔드 개발의 중요한 순간을 나타냅니다. 이 새로운 패러다임은 폼 처리를 극적으로 단순화하여 전통적인 클라이언트 측 상용구의 많은 부분을 서버로 이동시키고 사용자 상호 작용 및 데이터 변경을 관리하는 보다 통합되고 성능이 뛰어난 방법을 제공합니다. 이러한 도구를 채택함으로써 개발자는 훨씬 적은 노력과 명확하게 분리된 관심사로 더 강력하고 반응성이 뛰어난 유지 관리 가능한 애플리케이션을 구축할 수 있습니다. 서버 액션과 새로운 폼 훅은 웹에서 폼을 처리하는 방식을 재고하고 획기적으로 개선할 수 있는 진정한 힘을 제공합니다.

