폼 하나를 제대로 만들려면 얼마나 많은 것을 설치해야 할까. Formik, React Hook Form, Yup—이름만 나열해도 package.json이 무거워진다. dev.to에 공개된 분석에 따르면 이 의존성들이 번들에 추가하는 무게는 최소 30KB. 비즈니스 로직 한 줄 쓰기 전에 이미 사용자가 내려받아야 할 코드가 쌓인다. React 19는 이 문제를 라이브러리가 아닌 언어 수준에서 해결하겠다고 선언했다.
useFormStatus와 useFormState(혹은 useActionState)가 그 선언의 실체다. 두 훅은 폼 제출 생명주기를 React의 트랜지션 시스템 안으로 끌어들인다. pending, data, method, action—이 네 가지 프로퍼티만으로 로딩 스피너, 이중 제출 방지, 낙관적 UI 업데이트를 외부 상태 없이 처리할 수 있다. 핵심은 상태 업데이트가 React 스케줄러 안에서 원자적으로 일어난다는 점이다. 서드파티 라이브러리는 자체 상태 트리를 따로 굴리기 때문에 네트워크 요청이 이미 실패했는데 버튼은 여전히 "Submitting..."을 표시하는 레이스 컨디션이 생긴다. 훅이 React 내부에 있으면 이 간극 자체가 사라진다.
접근성 측면의 이득은 더 직접적이다. 서드파티 라이브러리가 ARIA 속성을 업데이트하는 시점과 실제 DOM 변화 사이의 미묘한 어긋남은 스크린 리더가 낡은 상태를 읽어주는 원인이 됐다. useFormStatus를 쓰면 aria-busy가 pending 상태와 정확히 같은 시점에 바뀐다. 별도의 동기화 코드 없이 스크린 리더가 올바른 타이밍에 올바른 메시지를 전달한다. 폼 상태 관리를 React에 위임하는 것이 곧 접근성을 React에 위임하는 셈이다.
구현 패턴에서 주의할 지점은 하나다. useFormStatus는 반드시 <form> 엘리먼트의 자식 컴포넌트 안에서 호출해야 한다. 폼 컴포넌트 자체에서 호출하면 파이버 트리가 제출 컨텍스트를 찾지 못한다. 제출 버튼을 별도 컴포넌트로 분리하는 것이 관례가 아닌 필수 조건인 이유다. 이 구조를 지키면 disabled 처리, 커서 스타일, 스피너 렌더링이 하나의 컴포넌트 안에서 응집된다. useFormState는 서버 액션의 반환값을 클라이언트 상태로 관리하며, 검증 로직을 서버에서 한 번만 실행하게 만들어 클라이언트-서버 이중 검증 문제도 함께 해결한다.
그런데 이런 패턴을 팀 전체에 일관되게 적용하려면 두 번째 질문이 생긴다. 더 나은 도구, 더 좋은 참조 구현을 어떻게 발견하고 검증하는가. 같은 dev.to에서 공개된 github-scout.sh 사례는 이 질문에 대한 흥미로운 답이다. Claude Code를 매일 아침 GitHub에 자동 순찰 보내 OSS와 스킬을 자동 스코링하고 브리프로 인젝션하는 파이프라인이다.
설계의 핵심은 비용 고정이다. GitHub API 크롤링(gh CLI, 무료)과 AI 스코링(claude -p, 유료)을 명시적으로 분리하고 후자의 입력 수를 MAX_CANDIDATES=30으로 캡핑한다. 4단계 파이프라인—CRAWL → DEDUP → ENRICH → SCORE+ROUTE—에서 토큰 비용은 마지막 단계에만 발생한다. 스킬 후보는 SKILL.md 파일 레이아웃으로 안전성을 기계적으로 판단해 자동 활성화 경로와 격리(quarantine) 경로로 나눈다. Bash나 스크립트가 포함된 항목(has_script=true)은 항상 사람의 리뷰를 거친다. 스코링이 실패하면 seen 원장에 기록하지 않아 다음 날 자동 재시도한다.
두 사례가 맞닿는 지점이 여기다. useFormStatus로 서드파티 의존성을 제거하는 것과, AI 에이전트로 더 나은 도구를 자동 발굴하는 것은 표면적으로 다른 주제처럼 보인다. 그러나 둘 다 같은 질문에서 출발한다—"이 복잡성이 정말 필요한가, 아니면 구조가 없어서 생긴 복잡성인가." 폼 라이브러리를 걷어내는 것은 React 19가 이미 해결한 문제를 외부에서 해결하려 했던 관성을 끊는 일이다. GitHub 순찰 에이전트를 띄우는 것은 "기억날 때 검색하겠다"는 비재현적 습관을 구조로 대체하는 일이다.
실용적 시사점은 두 층위로 나뉜다. 코드 레벨에서는 신규 폼 컴포넌트부터 useFormStatus + useFormState 조합으로 시작하고, 기존 Formik/React Hook Form 폼은 번들 임팩트가 큰 순서대로 마이그레이션 우선순위를 정하면 된다. 20~40KB의 번들 절감은 Core Web Vitals, 특히 LCP와 TBT에 직접 영향을 미친다. 워크플로우 레벨에서는 에이전트에게 반복 탐색을 위임할 때 비용 캡핑과 안전 라우팅을 설계에서 분리하지 말라는 교훈이 있다. github-scout.sh가 크롤과 스코링을 명시적으로 분리한 것처럼, 에이전트에 자율성을 주는 만큼 정확히 그 경계를 코드로 명문화해야 한다.
React 19의 폼 훅은 아직 많은 팀에서 관망 단계에 있다. 하지만 접근성과 번들 경량화를 동시에 요구하는 프로덕션 환경에서 이 두 훅의 채택은 선택이 아닌 수렴점이 될 가능성이 높다. AI 에이전트가 개발 도구 발굴을 자동화하는 흐름과 맞물리면, 더 나은 프리미티브를 더 빠르게 팀에 적용하는 루프가 만들어진다. 빠른 프로토타이핑과 접근 가능한 구현이 같은 사이클 안에서 돌아가는 것—그게 두 소스가 함께 가리키는 방향이다.