만약 React에서 Form을 사용할 때 리렌더링 문제에 대해 조사해 본 적이 있으시다면, React-Hook-Form에 대해 들어보셨을 것입니다.
React-Hook-Form은 React에서 Input 입력마다 상태가 변경되어 리렌더링 되는 문제를 Ref
를 이용하여 해결한 라이브러리입니다.
또한 유연하게 Form 컴포넌트를 구성할 수 있어서, 다양한 Form 상태를 관리할 수 있습니다.
그리고 최근, Next.js 14에서 Server Actions가 안정화 되었다는 발표가 나오면서, React-Hook-Form에서 Server Actions를 사용해보고 싶어졌습니다.
이번 글에선 제가 Next.js 14에서 React-Hook-Form으로 progressive enhancement를 유지하며 Serer Actions를 어떻게 처리하였는지 공유하고자 합니다.
React-Hook-Form은 아직까지 Server Actions를 지원하지 않고 있지만, 향 후 지원 예정인 점을 참고해주세요. 이번 글에선 7.51.4 버전을 사용하였습니다.
그전에 Server Actions에 대해 간단히 설명드리겠습니다.
Server Actions란?
Server Actions은 서버에서 실행되는 비동기 함수입니다.
RSC(React-Server-Component)에서 비동기로 서버 자원을 가져오거나, form
으로 양식 제출 및 데이터 변경을 처리할 수 있습니다.
Server Actions를 사용하면 다음의 이점을 가질 수 있습니다.
- 서버에서 동작: API 엔드포인트를 만들 필요가 없습니다. 서버에서 직접 실행하여 데이터를 가져오거나, 추가/수정/삭제할 수 있습니다.
- 비동기 실행: 비동기로 실행되는 함수입니다. Server / Client Component 모두 사용 가능하며 재사용성이 높습니다.
- progressive enhancement(점진적 향상): JS를 불러오는 중이거나, 비활성화된 상태더라도 양식을 제출할 수 있습니다.
- 캐싱 및 재검증 통합: 변경된 UI와 새로운 데이터를 한번의 왕복으로 가져올 수 있습니다.
환경 구성
아래 설명은 Next.js 14 버전을 사용하고 있다는 가정하에 작성되었습니다.
우선, React-Hook-Form과 검증을 위한 Zod를 설치합니다.
Form 작성
Server Action 함수 작성
우선 Form submit 시 작업을 처리하기 위한 Server Action 함수를 간단하게 작성해보겠습니다.
글 후반부에서 Zod 검증과 Error 핸들링을 추가하겠습니다.
/src/app/action.ts
Next.js에서 ServerAction을 사용하기 위한 Action 함수는 위가 기본 형태입니다.
Action 함수는 이전 상태 저장을 위한 prevState와 formData만 인자로 받을 수 있으므로 Argument부분은 고정입니다.
위에서 만든 action은 Input 유효성 검사나 에러, 성공 시 핸들링하지 않습니다. 아래부터 각 기능들을 추가해보겠습니다.
Form Page
React-Hook-Form은 Context 기반이여서 클라이언트 컴포넌트에서만 동작합니다. 파일 상단에 use client
를 선언하여 사용합니다.
방금 작성한 Action함수를 사용하기 위해 useFormState
를 사용하여 Form 상태를 관리합니다.
ServerActions를 사용하기 위해선 <form />
의 action
속성에 formAction
을 넣어주어야 합니다.
/src/app/client-side-form.tsx
Input Component
<input />
컴포넌트를 만들어보겠습니다.
React-Hook-Form은 Ref
를 통해 입력값을 관리하기 때문에, fowardRef를 사용하여 구현하여야만 합니다.
/src/components/ui/input.tsx
이 후, 필드 영역에 Input 컴포넌트를 사용하여 Form을 구성하면 됩니다.
React-Hook-Form의 Server-Actions
<form />
의 action을 통해 submit을 하여도, React-Hook-Form의 상태는 변하지 않습니다.
이는 React-Hook-Form이 Context 기반으로 동작하기 때문입니다.
그래서 저는 Custom Hook을 만들어서 Form의 상태를 관리하였습니다.
상태 종류 정의
먼저, 상태 종류를 관리하기 위한 type 파일을 작성합니다.
아래 중 VALIDATION_ERROR는 Zod를 통해 검증된 데이터가 아닌 경우 발생하는 에러입니다.
나머지는 사용할 상태를 커스텀하여 작성합니다.
/src/types/action.d.ts
Custom Hook 작성
다음, useForm
훅을 대체할 Custom Hook을 작성합니다.
Action 함수에서 반환된 state를 인자로 받아서 내부에서 상태를 관리하고, 에러 핸들링을 처리합니다.
또한, 성공 시 Client 단에서 처리할 수 있는 onSuccess
함수를 인자로 받아서 처리합니다.
/src/hooks/useFormAction.ts
Server Action 작성
우선 아래 전체 코드입니다.
/src/app/actions.ts
Form Zod 스키마 정의
우선 Form 내에서 사용할 스키마를 zod를 이용하여 정의해줍니다.
또한, React-Hook-Form에서 Type를 활용하여 사용하기 위해 FormValues
를 정의하고 export 합니다.
Zod를 활용한 유효성 검사
전달받은 FormData를 Zod로 검증합니다.
parse
함수를 사용할 경우 에러 발생 시 throw를 하기 때문에, safeParse
를 사용하여 에러 핸들링을 할 수 있습니다.
custom Hook에서 정의한대로, VALIDATION_ERROR
코드가 반환되면, Form field에 에러 상태를 추가합니다.
성공 or 실패 처리
성공 시, SUCCESS
코드와 함께 메시지를 반환합니다.
custom Hook에서 정의한대로, SUCCESS
코드가 반환되면, onSuccess
함수를 실행하고, Form을 초기화합니다.
Form Component
Form을 유연하게 사용하고, Form 메시지를 표시하기 위한 Form 컴포넌트를 작성합니다.
아래는 shadcn-ui
의 Form 컴포넌트를 사용하여 작성하였습니다.
/src/components/ui/form.tsx
Form Page 수정
마지막으로, Form 컴포넌트를 수정하여 Custom Hook을 사용하도록 변경합니다.
로딩 및 에러 상태 표시
action에서 반환 된 에러 처리 및 action 실행 중 로딩 상태를 표시해보겠습니다.
React-Query를 사용해보셨다면 isLoading
표시가 얼마나 간단한지 알 수 있을 것입니다.
위에서 isLoading만 확인하면 됩니다.
하지만, ServerActions`에선 위보단 약간 더 복잡합니다.
우선, Form의 Fields 영역을 다른 컴포넌트로 분리시켜야만 합니다.
그리고 useFormStatus
의 pending
상태를 확인하여 로딩 상태를 표시할 수 있습니다.
결과 확인
React-Hook-Form에서 Server Actions를 사용하기 위한 모든 과정을 마쳤습니다.
이제, 아래와 같은 결과를 확인할 수 있습니다.
또한, Zod는 Server Side에서만 사용했기 때문에, Client Side의 bundle size에 영향을 주지 않습니다.
위 작성된 소스 코드는 아래 Github 저장소에서 확인할 수 있습니다.
Github Repo: React-Hook-Form-Server-Actions
Bonus: Zod 에러 메시지 커스터마이징
Zod는 기본값으로 영문 에러 메시지를 반환합니다.
하지만, ZodError
객체를 통해 에러 메시지를 커스터마이징 할 수 있습니다.
/src/utils/zod.ts
이후, Zod 스키마에 errorMap
을 추가하여 커스터마이징한 에러 메시지를 반환할 수 있습니다.
/src/app/actions.ts
결론
여기까지 읽어주신 분들께 감사드립니다.
위 글은 대부분 제가 작성한 소스코드를 기반으로 작성되었습니다.
여러 문장의 글보다 직접 위 글에 있는 Custom Hook을 사용해보면 더욱 이해가 빠르게 될 것입니다.
Custom Hook은 최대한 복잡한 사용을 요구하지 않도록 작성해보았습니다.
또한 React-Hook-Form의 에러 상태를 활용하며, Ref를 활용한 렌더링 최적화 및 progressive enhancement를 유지하도록 작성하였습니다.
아직까진 ServerActions가 좋은 DX를 제공하진 않습니다.
하지만, 올해 출시 예정인 React 19부터는 공식적으로 form action이 추가되므로, 앞으로 계속 개선될 것으로 기대합니다.