설치
React Hook Form을 설치하려면 단 하나의 커맨드만 실행하면 됩니다. 이제 준비가 완료되었습니다.
npm install react-hook-form
예제
다음 코드는 기본 사용 예제를 보여줍니다:
import { useForm, SubmitHandler } from "react-hook-form"type Inputs = {example: stringexampleRequired: string}export default function App() {const {register,handleSubmit,watch,formState: { errors },} = useForm<Inputs>()const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)console.log(watch("example")) // 입력값을 이름으로 전달하여 확인return (/* "handleSubmit"은 "onSubmit"을 호출하기 전에 입력값을 검증합니다 */<form onSubmit={handleSubmit(onSubmit)}>{/* "register" 함수를 호출하여 입력을 훅에 등록합니다 */}<input defaultValue="test" {...register("example")} />{/* 필수 입력 또는 기타 표준 HTML 검증 규칙을 포함합니다 */}<input {...register("exampleRequired", { required: true })} />{/* 필드 검증이 실패하면 오류가 반환됩니다 */}{errors.exampleRequired && <span>이 필드는 필수입니다</span>}<input type="submit" /></form>)}
React 웹 비디오 튜토리얼
이 비디오 튜토리얼은 React Hook Form의 기본 사용법과 개념을 설명합니다.
필드 등록하기
React Hook Form의 핵심 개념 중 하나는 컴포넌트를 훅에 register
하는 것입니다. 이를 통해 폼 검증과 제출 시 해당 값에 접근할 수 있습니다.
참고: 각 필드는 등록 과정에서 키로 사용될 name
속성이 필수입니다.
import ReactDOM from "react-dom"import { useForm, SubmitHandler } from "react-hook-form"enum GenderEnum {female = "female",male = "male",other = "other",}interface IFormInput {firstName: stringgender: GenderEnum}export default function App() {const { register, handleSubmit } = useForm<IFormInput>()const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><label>First Name</label><input {...register("firstName")} /><label>Gender Selection</label><select {...register("gender")}><option value="female">female</option><option value="male">male</option><option value="other">other</option></select><input type="submit" /></form>)}
폼 검증 적용하기
React Hook Form은 HTML 표준 폼 검증과 일치하도록 만들어져 폼 검증을 쉽게 적용할 수 있습니다.
지원하는 검증 규칙 목록:
- required (필수)
- min (최소값)
- max (최대값)
- minLength (최소 길이)
- maxLength (최대 길이)
- pattern (패턴)
- validate (사용자 정의 검증)
각 규칙에 대한 자세한 내용은 register 섹션에서 확인할 수 있습니다.
import { useForm, SubmitHandler } from "react-hook-form"interface IFormInput {firstName: stringlastName: stringage: number}export default function App() {const { register, handleSubmit } = useForm<IFormInput>()const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("firstName", { required: true, maxLength: 20 })} /><input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} /><input type="number" {...register("age", { min: 18, max: 99 })} /><input type="submit" /></form>)}
기존 폼 통합하기
기존 폼을 통합하는 것은 간단해야 합니다. 중요한 단계는 컴포넌트의 ref
를 register
하고 입력 필드에 관련 props를 할당하는 것입니다.
import { Path, useForm, UseFormRegister, SubmitHandler } from "react-hook-form"interface IFormValues {"First Name": stringAge: number}type InputProps = {label: Path<IFormValues>register: UseFormRegister<IFormValues>required: boolean}// 다음 컴포넌트는 기존 Input 컴포넌트의 예시입니다.const Input = ({ label, register, required }: InputProps) => (<><label>{label}</label><input {...register(label, { required })} /></>)// ref를 전달하기 위해 React.forwardRef를 사용할 수도 있습니다.const Select = React.forwardRef<HTMLSelectElement,{ label: string } & ReturnType<UseFormRegister<IFormValues>>>(({ onChange, onBlur, name, label }, ref) => (<><label>{label}</label><select name={name} ref={ref} onChange={onChange} onBlur={onBlur}><option value="20">20</option><option value="30">30</option></select></>))const App = () => {const { register, handleSubmit } = useForm<IFormValues>()const onSubmit: SubmitHandler<IFormValues> = (data) => {alert(JSON.stringify(data))}return (<form onSubmit={handleSubmit(onSubmit)}><Input label="First Name" register={register} required /><Select label="Age" {...register("Age")} /><input type="submit" /></form>)}
UI 라이브러리와 통합하기
React Hook Form은 외부 UI 컴포넌트 라이브러리와 쉽게 통합할 수 있도록 설계되었습니다. 만약 컴포넌트가 입력 필드의 ref
를 노출하지 않는다면, 등록 과정을 처리해주는 Controller 컴포넌트를 사용해야 합니다.
import Select from "react-select"import { useForm, Controller, SubmitHandler } from "react-hook-form"import { Input } from "@material-ui/core"interface IFormInput {firstName: stringlastName: stringiceCreamType: { label: string; value: string }}const App = () => {const { control, handleSubmit } = useForm({defaultValues: {firstName: "",lastName: "",iceCreamType: {},},})const onSubmit: SubmitHandler<IFormInput> = (data) => {console.log(data)}return (<form onSubmit={handleSubmit(onSubmit)}><Controllername="firstName"control={control}render={({ field }) => <Input {...field} />}/><Controllername="iceCreamType"control={control}render={({ field }) => (<Select{...field}options={[{ value: "chocolate", label: "Chocolate" },{ value: "strawberry", label: "Strawberry" },{ value: "vanilla", label: "Vanilla" },]}/>)}/><input type="submit" /></form>)}
제어된 입력 통합하기
이 라이브러리는 비제어 컴포넌트와 기본 HTML 입력을 주로 사용합니다. 하지만 React-Select, AntD, MUI와 같은 외부 제어 컴포넌트를 사용하지 않기란 쉽지 않습니다. 이를 간단하게 만들기 위해, Controller라는 래퍼 컴포넌트를 제공합니다. 이 컴포넌트는 통합 과정을 간소화하면서도 커스텀 등록을 자유롭게 사용할 수 있도록 해줍니다.
컴포넌트 API 사용하기
import { useForm, Controller, SubmitHandler } from "react-hook-form"import { TextField, Checkbox } from "@material-ui/core"interface IFormInputs {TextField: stringMyCheckbox: boolean}function App() {const { handleSubmit, control, reset } = useForm<IFormInputs>({defaultValues: {MyCheckbox: false,},})const onSubmit: SubmitHandler<IFormInputs> = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><Controllername="MyCheckbox"control={control}rules={{ required: true }}render={({ field }) => <Checkbox {...field} />}/><input type="submit" /></form>)}
Hooks API 사용하기
import * as React from "react"import { useForm, useController, UseControllerProps } from "react-hook-form"type FormValues = {FirstName: string}function Input(props: UseControllerProps<FormValues>) {const { field, fieldState } = useController(props)return (<div><input {...field} placeholder={props.name} /><p>{fieldState.isTouched && "Touched"}</p><p>{fieldState.isDirty && "Dirty"}</p><p>{fieldState.invalid ? "invalid" : "valid"}</p></div>)}export default function App() {const { handleSubmit, control } = useForm<FormValues>({defaultValues: {FirstName: "",},mode: "onChange",})const onSubmit = (data: FormValues) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><Input control={control} name="FirstName" rules={{ required: true }} /><input type="submit" /></form>)}
글로벌 상태와 통합하기
이 라이브러리는 상태 관리 라이브러리에 의존할 필요가 없지만, 쉽게 통합할 수 있습니다.
import { useForm } from "react-hook-form"import { connect } from "react-redux"import updateAction from "./actions"export default function App(props) {const { register, handleSubmit, setValue } = useForm({defaultValues: {firstName: "",lastName: "",},})// 데이터를 Redux 스토어에 제출const onSubmit = (data) => props.updateAction(data)return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("firstName")} /><input {...register("lastName")} /><input type="submit" /></form>)}// 컴포넌트를 Redux와 연결connect(({ firstName, lastName }) => ({ firstName, lastName }),updateAction)(YourForm)
에러 처리
React Hook Form은 폼에서 발생한 에러를 보여주기 위해 errors
객체를 제공합니다. errors
의 타입은 주어진 유효성 검사 제약 조건을 반환합니다. 다음 예제는 필수 입력 유효성 검사 규칙을 보여줍니다.
import { useForm } from "react-hook-form"export default function App() {const {register,formState: { errors },handleSubmit,} = useForm()const onSubmit = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><input{...register("firstName", { required: true })}aria-invalid={errors.firstName ? "true" : "false"}/>{errors.firstName?.type === "required" && (<p role="alert">First name is required</p>)}<input{...register("mail", { required: "Email Address is required" })}aria-invalid={errors.mail ? "true" : "false"}/>{errors.mail && <p role="alert">{errors.mail.message}</p>}<input type="submit" /></form>)}
서비스와 통합하기
React Hook Form을 서비스와 통합하려면 라이브러리의 내장된 제출 처리 기능을 사용할 수 있습니다. <Form />
컴포넌트를 사용하면 폼 데이터를 API 엔드포인트나 다른 서비스로 쉽게 보낼 수 있습니다. Form 컴포넌트에 대해 더 알아보기.
import { Form } from "react-hook-form"function App() {const { register, control } = useForm()return (<Formaction="/api/save" // FormData와 함께 POST 요청 전송// encType={'application/json'} JSON 객체로 전환할 수도 있음onSuccess={() => {alert("Your application is updated.")}}onError={() => {alert("Submission has failed.")}}control={control}><input {...register("firstName", { required: true })} /><input {...register("lastName", { required: true })} /><button>Submit</button></Form>)}
스키마 유효성 검증
Yup, Zod, Superstruct, Joi와 같은 스키마 기반 폼 유효성 검증을 지원합니다. useForm에 schema
를 옵션으로 전달하면 입력 데이터를 스키마에 대해 검증하고 에러 또는 유효한 결과를 반환합니다.
1단계: 프로젝트에 Yup
을 설치합니다.
npm install @hookform/resolvers yup
2단계: 유효성 검증을 위한 스키마를 준비하고 React Hook Form으로 입력을 등록합니다.
import { useForm } from "react-hook-form"import { yupResolver } from "@hookform/resolvers/yup"import * as yup from "yup"const schema = yup.object({firstName: yup.string().required(),age: yup.number().positive().integer().required(),}).required()export default function App() {const {register,handleSubmit,formState: { errors },} = useForm({resolver: yupResolver(schema),})const onSubmit = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("firstName")} /><p>{errors.firstName?.message}</p><input {...register("age")} /><p>{errors.age?.message}</p><input type="submit" /></form>)}
React Native
React Native에서도 동일한 성능 향상과 개선 효과를 얻을 수 있습니다. 입력 컴포넌트와 통합하려면 Controller
로 감싸면 됩니다.
import { Text, View, TextInput, Button, Alert } from "react-native"import { useForm, Controller } from "react-hook-form"export default function App() {const {control,handleSubmit,formState: { errors },} = useForm({defaultValues: {firstName: "",lastName: "",},})const onSubmit = (data) => console.log(data)return (<View><Controllercontrol={control}rules={{required: true,}}render={({ field: { onChange, onBlur, value } }) => (<TextInputplaceholder="First name"onBlur={onBlur}onChangeText={onChange}value={value}/>)}name="firstName"/>{errors.firstName && <Text>This is required.</Text>}<Controllercontrol={control}rules={{maxLength: 100,}}render={({ field: { onChange, onBlur, value } }) => (<TextInputplaceholder="Last name"onBlur={onBlur}onChangeText={onChange}value={value}/>)}name="lastName"/><Button title="Submit" onPress={handleSubmit(onSubmit)} /></View>)}
TypeScript
React Hook Form은 TypeScript
로 만들어졌으며, 폼 값에 대한 타입을 지원하기 위해 FormData
타입을 정의할 수 있습니다.
import * as React from "react"import { useForm } from "react-hook-form"type FormData = {firstName: stringlastName: string}export default function App() {const {register,setValue,handleSubmit,formState: { errors },} = useForm<FormData>()const onSubmit = handleSubmit((data) => console.log(data))// firstName과 lastName은 올바른 타입을 가짐return (<form onSubmit={onSubmit}><label>First Name</label><input {...register("firstName")} /><label>Last Name</label><input {...register("lastName")} /><buttontype="button"onClick={() => {setValue("lastName", "luo") // ✅setValue("firstName", true) // ❌: true는 string 타입이 아님errors.bill // ❌: bill 프로퍼티는 존재하지 않음}}>SetValue</button></form>)}
디자인과 철학
React Hook Form의 디자인과 철학은 사용자와 개발자 경험에 초점을 맞춥니다. 이 라이브러리는 성능을 세밀하게 조정하고 접근성을 개선하여 사용자에게 더 원활한 상호작용 경험을 제공하는 것을 목표로 합니다. 성능 향상을 위한 몇 가지 방법은 다음과 같습니다:
- 프록시를 통한 폼 상태 구독 모델 도입
- 불필요한 계산 방지
- 필요한 경우 컴포넌트 리렌더링 분리
이러한 개선 사항은 사용자가 애플리케이션과 상호작용할 때 더 나은 사용자 경험을 제공합니다. 개발자 측면에서는 내장된 유효성 검사를 제공하며, HTML 표준과 밀접하게 연동되어 강력한 유효성 검사 방법과 스키마 유효성 검사를 기본적으로 통합할 수 있습니다. 또한, TypeScript를 활용하여 강력한 타입 검사를 지원함으로써 개발자가 견고한 폼 솔루션을 구축할 수 있도록 초기 빌드 단계에서 피드백을 제공합니다.
Bill이 진행한 다음 강연에서는 몇 가지 아이디어와 디자인 패턴을 소개합니다:
여러분의 지원에 감사드립니다
React Hook Form이 프로젝트에서 유용하다면, GitHub에서 스타를 눌러 지원해 주세요.