useFormStatus와 useFormState가 완성되면서, 서버 액션을 중심으로 한 새로운 폼 처리 패러다임의 모든 조각이 맞춰졌다. React Core Team은 이 변화가 가져온 혁신을 가장 명확하게 보여주기 위해, ‘이전’과 ‘이후’의 코드를 나란히 비교하는 문서를 작성하기 시작했다.
그 결과물은 한 편의 드라마와도 같았다.
[Before] 서버 액션이 없던 시절: 수많은 상태와 핸들러의 미로
화면에는 로그인 폼을 구현한 전형적인 React 18 이전 시대의 코드가 펼쳐졌다.
// LoginForm_Old.js
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export function LoginFormOld() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (!response.ok) {
setError(data.message);
setIsSubmitting(false);
} else {
router.push('/dashboard');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '로그인 중...' : '로그인'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</form>
);
}
코드는 길고 복잡했다.
- 입력값 관리를 위한
email,password상태. - 에러 메시지 표시를 위한
error상태. - 제출 중 UI 처리를 위한
isSubmitting상태. - 총 4개의
useState가 필요했다. - 폼의 기본 동작을 막는
e.preventDefault(). - 상태를 JSON으로 변환하고
fetch를 호출하는 수동적인 API 통신 로직. - 응답 결과에 따라
error상태를 설정하거나 라우터를 통해 페이지를 이동시키는 분기 처리.
이 모든 것이 단지 로그인 폼 하나를 위해 필요한 코드였다.
[After] React 19의 시대: 선언적인 우아함
다음으로, 똑같은 기능을 하는 React 19의 코드가 나타났다.
// actions.js (Server File)
'use server';
// ... (로그인 로직, 성공 시 redirect, 실패 시 메시지 반환)
// LoginForm_New.js (Client Component)
'use client';
import { useFormState } from 'react-dom';
import { loginAction } from './actions';
import { SubmitButton } from './SubmitButton'; // useFormStatus를 사용하는 버튼
const initialState = { message: null };
export function LoginFormNew() {
const [state, formAction] = useFormState(loginAction, initialState);
return (
<form action={formAction}>
<input type="email" name="email" />
<input type="password" name="password" />
<SubmitButton />
{state?.message && <p style={{ color: 'red' }}>{state.message}</p>}
</form>
);
}
회의실은 조용해졌다. 그 침묵은 코드의 압도적인 간결함이 주는 경이로움 때문이었다.
- 입력값, 에러, 제출 중 상태를 관리하던 4개의
useState가 모두 사라지고,useFormState하나로 통합되었다. - 복잡했던
handleSubmit함수 전체가 사라졌다. fetch호출과 JSON 직렬화 코드가 사라졌다.- 제출 버튼의 로딩 상태 관리는
SubmitButton컴포넌트 내부로 완벽하게 캡슐화되었다.
코드는 이제 자신이 ‘무엇을 하는지’만 선언적으로 보여줄 뿐, ‘어떻게 하는지’에 대한 지저분한 구현은 모두 React의 뒤편으로 사라졌다.
이 Before & After 비교 문서는 단순한 코드 조각이 아니었다. 그것은 React 19가 웹 개발의 복잡성을 어떻게 정복했는지를 보여주는 가장 강력한 증거였다. 개발자들은 더 이상 상태 관리의 미로에서 헤맬 필요가 없었다. 그들은 이제 비즈니스 로직이라는 본질에 집중할 수 있는 자유를 얻게 될 것이었다. 이 극명한 대비는 앞으로 수많은 개발자들이 새로운 패러다임을 받아들이게 될 결정적인 계기가 될 것이 분명했다.


