React 19 RC와 useActionState 훅 실험

배경

사내 어드민 페이지의 form 처리 로직이 복잡해지면서 리팩토링이 필요했다. 마침 React 19 RC가 공개되어 useActionState(구 useFormState)를 테스트해보기로 했다.

기존 코드의 문제

기존에는 useState, useEffect, try-catch를 조합해서 loading, error, success 상태를 모두 수동으로 관리했다.

const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const handleSubmit = async (e: FormEvent) => {
  e.preventDefault();
  setLoading(true);
  setError(null);
  try {
    await updateUser(formData);
  } catch (err) {
    setError(err.message);
  } finally {
    setLoading(false);
  }
};

useActionState 적용

React 19의 useActionState는 이 패턴을 훅 하나로 처리한다.

const [state, submitAction, isPending] = useActionState(
  async (prevState, formData: FormData) => {
    try {
      const result = await updateUser({
        name: formData.get('name') as string,
      });
      return { success: true, message: '저장 완료' };
    } catch (error) {
      return { success: false, message: error.message };
    }
  },
  { success: false, message: '' }
);

return (
  <form action={submitAction}>
    <input name="name" />
    <button disabled={isPending}>저장</button>
    {state.message && <div>{state.message}</div>}
  </form>
);

주의사항

  1. action prop에 전달하면 자동으로 FormData를 생성해준다
  2. isPending은 실제 async 함수 실행 시간만 추적한다
  3. 아직 RC라서 프로덕션 적용은 보류했다

결론

form 처리 보일러플레이트가 확실히 줄어들었다. 정식 릴리즈 후 점진적으로 마이그레이션 계획을 세울 예정이다.