리액트 훅 폼
리액트 훅 폼 (React Hook Form)
웹 애플리케이션에서 가장 흔하게 만들면서도 가장 복잡한 것 중 하나가 바로 폼이에요. 이 가이드에서는 react-hook-form과 zod를 함께 써서 폼을 어떻게 구성하는지 살펴볼게요. Radix UI 컴포넌트를 조합해 접근성 좋은 폼을 만드는 FormField 컴포넌트도 사용할 거예요.
출처: 문서
본문
폼은 까다로워요. 웹 애플리케이션에서 가장 흔히 만들게 되는 것 중 하나이면서 가장 복잡한 것 중 하나이기도 하죠.
잘 설계된 HTML 폼은 이러해요:
- 구조가 잘 잡혀 있고 의미적으로 올바르다.
- (키보드로) 사용하고 탐색하기 쉽다.
- ARIA 속성과 적절한 label로 접근성이 확보된다.
- 클라이언트 및 서버 측 검증을 지원한다.
- 스타일이 잘 정돈되어 있고 앱의 나머지 부분과 일관된다.
이 가이드에서는 react-hook-form과 zod로 폼을 만드는 법을 살펴볼게요. Radix UI 컴포넌트를 사용해 접근성 좋은 폼을 조합하기 위해 <FormField> 컴포넌트를 쓸 거예요.
기능 (Features)
<Form /> 컴포넌트는 react-hook-form 라이브러리를 감싸는 래퍼예요. 몇 가지를 제공하죠:
- 폼을 만들기 위한 조합(composable) 컴포넌트.
- 제어되는 폼 필드를 만들기 위한
<FormField />컴포넌트. zod를 사용한 폼 검증.- 접근성과 오류 메시지 처리.
- 고유한 ID 생성을 위한
React.useId()사용. - 상태에 따라 폼 필드에 올바른
aria속성을 적용. - 모든 Radix UI 컴포넌트와 함께 동작하도록 설계됨.
- 원하는 스키마 라이브러리를 가져다 쓸 수 있어요. 우리는
zod를 사용하지만 원하는 거 아무거나 써도 돼요. - 마크업과 스타일을 완전히 제어할 수 있어요.
구조 (Anatomy)
<Form>
<FormField
control={...}
name="..."
render={() => (
<FormItem>
<FormLabel />
<FormControl>
{ /* Your form field */}
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
)}
/>
</Form>
예시 (Example)
const form = useForm()
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>This is your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
설치하기
명령어 (Command)
npx shadcn@latest add form
npm install @radix-ui/react-label @radix-ui/react-slot react-hook-form @hookform/resolvers zod
사용하기
폼 스키마 만들기 (Create a form schema)
Zod 스키마를 사용해 폼의 형태를 정의해요. Zod 사용법을 더 자세히 알고 싶다면 Zod 문서를 참고하세요.
"use client"
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2).max(50),
})
폼 정의하기 (Define a form)
react-hook-form의 useForm 훅을 사용해 폼을 만들어요.
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
export function ProfileForm() {
// 1. Define your form.
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values)
}
}
FormField는 제어되는 컴포넌트를 사용하므로 필드에 기본값을 제공해야 해요. 제어되는 컴포넌트에 대해 더 알고 싶다면 React Hook Form 문서를 참고해 주세요.
폼 만들기 (Build your form)
이제 <Form /> 컴포넌트로 우리의 폼을 만들 수 있어요.
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
export function ProfileForm() {
// ...
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
완료 (Done)
끝났어요. 이제 완전히 접근성이 확보되고, 타입 안전하며 클라이언트 측 검증이 되는 폼이 생겼어요.
예시 (Examples)
다른 컴포넌트와 함께 <Form /> 컴포넌트를 어떻게 사용하는지 더 많은 예시를 보려면 다음 링크를 확인해 주세요:
더 알아보기 (Learn more)
- react-hook-form — 폼 상태와 검증을 관리하는 라이브러리예요.
- Zod — 타입 안전한 스키마 검증 라이브러리예요.