# Get Started > 리액트 훅 form으로 간단한 양식 유효성 검사. ## 설치 {#Quickstart} React Hook Form을 설치하는 것은 단 한 번의 명령어로 가능합니다. ```bash npm install react-hook-form ``` ## 예제 다음 코드 예제는 기본 사용법을 보여줍니다: ```tsx import { useForm, SubmitHandler } from "react-hook-form" type Inputs = { example: string exampleRequired: string } export default function App() { const { register, handleSubmit, watch, formState: { errors }, } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) console.log(watch("example")) // 입력 값 관찰 return ( /* "handleSubmit"은 "onSubmit"을 호출하기 전에 입력 값을 validation합니다. */
{/* "register" 함수를 호출하여 입력을 훅에 register합니다. */} {/* HTML 표준 validation 규칙을 사용하여 입력 validation을 포함합니다. */} {/* field validation에 실패하면 errors가 반환됩니다. */} {errors.exampleRequired && This field is required}
) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, watch, formState: { errors }, } = useForm() const onSubmit = (data) => console.log(data) console.log(watch("example")) // 입력 값 관찰 return ( /* "handleSubmit"은 "onSubmit"을 호출하기 전에 입력 값을 validation합니다. */
{/* "register" 함수를 호출하여 입력을 훅에 register합니다. */} {/* HTML 표준 validation 규칙을 사용하여 입력 validation을 포함합니다. */} {/* field validation에 실패하면 errors가 반환됩니다. */} {errors.exampleRequired && This field is required}
) } ``` ## React 웹 비디오 튜토리얼 {#ReactWebVideoTutorial} 이 비디오 튜토리얼은 React Hook Form의 기본 사용법과 개념을 설명합니다. ## Register field {#Registerfields} React Hook Form의 주요 개념 중 하나는 컴포넌트를 훅에 **`register`** 하는 것입니다. 이렇게 하면 해당 컴포넌트의 값이 form validation 및 제출에 사용될 수 있습니다. **참고:** 각 field는 register 과정에서 `name`이라는 키가 **`필요`** 합니다. ```tsx import ReactDOM from "react-dom" import { useForm, SubmitHandler } from "react-hook-form" enum GenderEnum { female = "female", male = "male", other = "other", } interface IFormInput { firstName: string gender: GenderEnum } export default function App() { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) return (
) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm() const onSubmit = (data) => console.log(data) return (
) } ``` ## Apply validation {#Applyvalidation} React Hook Form은 기존 [HTML 표준 form validation](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation)을 따름으로써 form validation을 쉽게 만듭니다. 지원되는 validation 규칙 목록: - required - min - max - minLength - maxLength - pattern - validate 각 규칙에 대한 자세한 내용은 [register 섹션](/docs#register)에서 확인할 수 있습니다. ```tsx import { useForm, SubmitHandler } from "react-hook-form" interface IFormInput { firstName: string lastName: string age: number } export default function App() { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) return (
) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm() const onSubmit = (data) => console.log(data) return (
) } ``` ## 기존 form 통합 {#Integratinganexistingform} 기존 form을 통합하는 것은 간단합니다. 중요한 단계는 컴포넌트의 `ref`를 `register`하고 관련 속성을 입력에 할당하는 것입니다. ```tsx import { Path, useForm, UseFormRegister, SubmitHandler } from "react-hook-form" interface IFormValues { "First Name": string Age: number } type InputProps = { label: Path register: UseFormRegister required: boolean } // 다음 컴포넌트는 기존 Input 컴포넌트의 예시입니다. const Input = ({ label, register, required }: InputProps) => ( <> ) // React.forwardRef를 사용하여 ref를 전달할 수도 있습니다. const Select = React.forwardRef< HTMLSelectElement, { label: string } & ReturnType> >(({ onChange, onBlur, name, label }, ref) => ( <> )) const App = () => { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => { alert(JSON.stringify(data)) } return (
) } ``` ```javascript import { useForm } from "react-hook-form" // 다음 컴포넌트는 기존 Input 컴포넌트의 예시입니다. const Input = ({ label, register, required }) => ( <> ) // React.forwardRef를 사용하여 ref를 전달할 수도 있습니다. const Select = React.forwardRef(({ onChange, onBlur, name, label }, ref) => ( <> )) const App = () => { const { register, handleSubmit } = useForm() const onSubmit = (data) => { alert(JSON.stringify(data)) } return (
) } ``` ## UI 라이브러리와 통합 {#IntegratingwithUIlibraries} React Hook Form은 외부 UI 컴포넌트 라이브러리와 쉽게 통합할 수 있습니다. 컴포넌트가 입력의 `ref`를 노출하지 않는 경우 Controller 컴포넌트를 사용하여 register 과정을 처리할 수 있습니다. ```typescript import Select from "react-select" import { useForm, Controller, SubmitHandler } from "react-hook-form" import { Input } from "@material-ui/core" interface IFormInput { firstName: string lastName: string iceCreamType: { label: string; value: string } } const App = () => { const { control, handleSubmit } = useForm({ defaultValues: { firstName: "", lastName: "", iceCreamType: {}, }, }) const onSubmit: SubmitHandler = (data) => { console.log(data) } return (
} /> ( ) } ``` ```javascript import Select from "react-select" import { useForm, Controller } from "react-hook-form" import { Input } from "@material-ui/core" const App = () => { const { control, handleSubmit } = useForm({ defaultValues: { firstName: "", select: {}, }, }) const onSubmit = (data) => console.log(data) return (
} /> ( ) } ``` ## 제어된 입력 통합 {#IntegratingControlledInputs} 이 라이브러리는 비제어 컴포넌트와 기본 HTML 입력을 사용합니다. 그러나 [shadcn/ui](https://ui.shadcn.com/docs/components/form), [React-Select](https://github.com/JedWatson/react-select), [AntD](https://github.com/ant-design/ant-design) 및 [MUI](https://mui.com/)와 같은 외부 제어 컴포넌트와 작업하는 것을 피하기는 어렵습니다. 이를 간단하게 만들기 위해, 우리는 [Controller](/docs#Controller) 컴포넌트를 제공하여 register 과정을 간소화하면서도 사용자 정의 register을 사용할 수 있는 자유를 제공합니다. #### 컴포넌트 API 사용 ```tsx import { useForm, Controller, SubmitHandler } from "react-hook-form" import { TextField, Checkbox } from "@material-ui/core" interface IFormInputs { TextField: string MyCheckbox: boolean } function App() { const { handleSubmit, control, reset } = useForm({ defaultValues: { MyCheckbox: false, }, }) const onSubmit: SubmitHandler = (data) => console.log(data) return (
} /> ) } ``` ```javascript import { TextField } from "@material-ui/core" import { useController, useForm } from "react-hook-form" function Input({ control, name }) { const { field, fieldState: { invalid, isTouched, isDirty }, formState: { touchedFields, dirtyFields }, } = useController({ name, control, rules: { required: true }, }) return ( ) } ``` ```javascript import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" import { Form, FormControl, FormField } 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 InputForm() { const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { username: "", }, }) function onSubmit(data: z.output) { console.log(data) } return (
( )} /> ) } ``` #### 훅 API 사용 ```tsx import * as React from "react" import { useForm, useController, UseControllerProps } from "react-hook-form" type FormValues = { FirstName: string } function Input(props: UseControllerProps) { const { field, fieldState } = useController(props) return (

{fieldState.isTouched && "Touched"}

{fieldState.isDirty && "Dirty"}

{fieldState.invalid ? "invalid" : "valid"}

) } export default function App() { const { handleSubmit, control } = useForm({ defaultValues: { FirstName: "", }, mode: "onChange", }) const onSubmit = (data: FormValues) => console.log(data) return (
) } ``` ```javascript import { TextField } from "@material-ui/core" import { useController, useForm } from "react-hook-form" function Input({ control, name }) { const { field, fieldState: { invalid, isTouched, isDirty }, formState: { touchedFields, dirtyFields }, } = useController({ name, control, rules: { required: true }, }) return ( ) } ``` ## 전역 상태와 통합 {#Integratingwithglobalstate} 이 라이브러리는 상태 관리 라이브러리에 의존할 필요가 없지만, 쉽게 통합할 수 있습니다. ```javascript 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 (
) } // Redux와 컴포넌트를 연결 connect( ({ firstName, lastName }) => ({ firstName, lastName }), updateAction )(YourForm) ``` ## Handle errors {#Handleerrors} React Hook Form은 form의 오류를 표시하기 위한 `errors` 객체를 제공합니다. `errors` 타입은 주어진 validation 제약 조건을 반환합니다. 다음 예제는 필수 validation 규칙을 보여줍니다. ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, formState: { errors }, handleSubmit, } = useForm() const onSubmit = (data) => console.log(data) return (
{errors.firstName?.type === "required" && (

First name is required

)} {errors.mail &&

{errors.mail.message}

}
) } ``` ## 서비스와 통합 {#Integratingwithservices} React Hook Form을 서비스와 통합하려면, 라이브러리의 내장 제출 처리를 사용할 수 있습니다. `` 컴포넌트는 form 데이터를 API 엔드포인트나 다른 서비스에 쉽게 보낼 수 있습니다. [Form 컴포넌트에 대해 자세히 알아보기](/docs/useform/form). ```javascript import { Form } from "react-hook-form" function App() { const { register, control } = useForm() return (
{ alert("Your application is updated.") }} onError={() => { alert("Submission has failed.") }} control={control} >
) } ``` ## 스키마 validation {#SchemaValidation} 우리는 [Yup](https://github.com/jquense/yup), [Zod](https://github.com/vriad/zod) , [Superstruct](https://github.com/ianstormtaylor/superstruct) 및 [Joi](https://github.com/hapijs/joi)와 같은 스키마 기반 form validation도 지원합니다. `schema`를 [useForm](/docs#useForm)의 옵션으로 전달하여 입력 데이터를 스키마에 맞게 validation하고, [errors](/docs#errors) 또는 유효한 결과를 반환합니다. **단계 1:** 프로젝트에 Yup을 설치합니다. ```bash npm install @hookform/resolvers yup ``` **단계 2:** validation을 위한 스키마를 준비하고 React Hook Form에 입력을 register합니다. ```javascript 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 (

{errors.firstName?.message}

{errors.age?.message}

) } ``` ## React Native {#ReactNative} React Native에서도 동일한 성능 향상 및 개선을 얻을 수 있습니다. 입력 컴포넌트와 통합하려면 `Controller`로 감쌀 수 있습니다. ```javascript expo 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 ( ( )} name="firstName" /> {errors.firstName && This is required.} ( )} name="lastName" /> ) } ``` ## 디자인과 철학 {#Designandphilosophy} React Hook Form의 디자인과 철학은 사용자와 개발자의 경험에 중점을 둡니다. 라이브러리는 성능을 미세하게 조정하고 접근성을 향상시켜 사용자에게 더 부드러운 상호 작용 경험을 제공하는 것을 목표로 합니다. 성능 향상의 일부는 다음과 같습니다: - 프록시를 통한 form 상태 구독 모델 도입 - 불필요한 계산 방지 - 필요한 경우에만 컴포넌트 리렌더링 격리 전반적으로, 이는 사용자가 애플리케이션과 상호 작용할 때 사용자 경험을 향상시킵니다. 개발자를 위해, 내장된 validation을 제공하고 HTML 표준과 긴밀하게 일치하여 강력한 validation 방법과 스키마 validation과의 통합을 더욱 확장할 수 있도록 합니다. 또한, TypeScript의 강력한 타입 검사를 통해 개발자에게 초기 빌드 시간 피드백을 제공하여 견고한 form 솔루션을 구축하는 데 도움을 줍니다. 다음 논의에서는 [Bill](https://twitter.com/bluebill1049)이 일부 아이디어와 디자인 패턴을 소개합니다: --- # useForm > 폼 유효성 검사를 위한 리액트 훅 ## \ `useForm:` [`UseFormProps`](/ts#UseFormProps) `useForm`은 폼을 쉽게 관리하기 위한 사용자 정의 훅입니다. **선택적** 인자로 하나의 객체를 받습니다. 다음 예제에서는 모든 속성과 기본값을 함께 확인할 수 있습니다. **일반적인 속성:** | Option | Description | | ------------------------------------------------------- | ---------------------------------------------------------------------- | | [mode](#mode) | 제출 **전** 유효성 검사 전략. | | [reValidateMode](#reValidateMode) | 제출 **후** 유효성 검사 전략. | | [defaultValues](#defaultValues) | 폼의 기본값이며, 이 값은 캐시됩니다. | | [values](#values) | 폼 값을 업데이트하기 위한 반응형 값. | | [errors](#errors) | 서버에서 반환된 에러로 폼을 업데이트합니다. **⚠ 중요:** 무한 리렌더링을 방지하기 위해 errors 객체의 참조를 안정적으로 유지하세요. | | [resetOptions](#resetOptions) | 새로운 폼 값을 업데이트할 때 폼 상태 업데이트를 초기화할 수 있는 옵션. | | [criteriaMode](#criteriaMode) | 모든 유효성 검사 에러를 한 번에 노출하거나 하나씩 노출. | | [shouldFocusError](#shouldFocusError) | 내장된 포커스 관리를 활성화하거나 비활성화. | | [delayError](#delayError) | 에러가 즉시 나타나는 것을 지연. | | [shouldUseNativeValidation](#shouldUseNativeValidation) | 브라우저 내장 폼 제약 조건 API 사용. | | [shouldUnregister](#shouldUnregister) | 언마운트 후 입력의 등록 취소(unregister)를 활성화하거나 비활성화. | | [disabled](#disabled) | 전체 폼과 해당 폼에 포함된 모든 입력을 비활성화합니다. | **스키마 유효성 검사 속성:** | Option | Description | | --------------------- | ----------------------------------------------------- | | [resolver](#resolver) | 선호하는 스키마 유효성 검사 라이브러리와 통합됩니다. | | [context](#context) | 스키마 유효성 검사를 위해 제공할 컨텍스트 객체입니다. | ### Props --- #### mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit' {#mode} --- 이 옵션을 사용하면 사용자가 폼을 제출하기 전에 유효성 검사 전략을 세울 수 있습니다. 유효성 검사는 [`handleSubmit`](/docs/useform/handlesubmit) 함수를 호출하여 트리거되는 `onSubmit` 이벤트 동안 발생합니다. | Name | Type | Description | | --------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | onSubmit | string | 유효성 검사는 `submit` 이벤트에서 트리거되며, 입력은 `onChange` 이벤트 리스너와 연결되어 자신을 재검증합니다. | | onBlur | string | 유효성 검사는 `blur` 이벤트에서 트리거됩니다. | | onChange | string | 유효성 검사는 각 입력에 대한 `change` 이벤트에서 트리거되어 여러 번의 리렌더링을 발생시킵니다. 경고: 이는 성능에 상당한 영향을 미칠 수 있습니다. | | onTouched | string | 유효성 검사는 처음에 첫 번째 `blur` 이벤트에서 트리거됩니다. 그 이후에는, 모든 `change` 이벤트에서 트리거됩니다. **참고:** `Controller`와 함께 사용할 때는, `render` 속성에 `onBlur`를 연결해야 합니다. | | all | string | 유효성 검사는 `blur`와 `change` 이밴트에서 모두 트리거됩니다. | #### reValidateMode: onChange | onBlur | onSubmit = 'onChange' {#reValidateMode} --- 이 옵션을 사용하면 사용자가 폼을 제출한 **후** (즉, `onSubmit` 이벤트와 [`handleSubmit`](/docs/useform/handlesubmit) 함수가 실행된 후) 에러가 있는 입력이 다시 검증될 때의 유효성 검사 전략을 세울 수 있습니다. 기본적으로 재검증은 입력 change 이벤트 중 발생합니다. #### defaultValues: `FieldValues | () => Promise` {#defaultValues} --- `defaultValues` 속성은 전체 폼을 기본값으로 채워줍니다. 이는 기본값의 동기 및 비동기 할당을 모두 지원합니다. 입력의 기본값을 설정할 때 `defaultValue` 나 `defaultChecked`를 사용할 수 있지만 [(공식 리액트 문서에 자세히 설명)](https://react.dev/reference/react-dom/components/input), 전체 폼에는 `defaultValues`를 사용하는 것이 **좋습니다**. ```javascript useForm({ defaultValues: { firstName: '', lastName: '' } }) // 기본값을 비동기적으로 설정 useForm({ defaultValues: async () => fetch('/api-endpoint'); }) ``` - `undefined`를 기본값으로 제공하면 제어 컴포넌트의 기본 상태와 충돌하므로, `undefined`는 기본값으로 제공하면 **안됩니다**. - `defaultValues`는 캐시됩니다. 이를 재설정하려면 [reset](/docs/useform/reset) API를 사용하세요. - 기본적으로 `defaultValues`는 submission 결과에 포함됩니다. - `defaultValues`로 `Moment` 나 `Luxon`과 같은 프로토타입 메서드를 포함하는 사용자 정의 객체는 사용하지 않는 것이 좋습니다. - 폼 데이터를 포함하는 다른 옵션도 있습니다: ```javascript // 숨겨진 입력 추가 ``` ```javascript // onSubmit 시 데이터 포함 const onSubmit = (data) => { const output = { ...data, others: "others", } } ``` #### values: FieldValues {#values} --- `values` 속성은 변경 사항에 반응하여 폼 값을 업데이트하며, 이는 폼이 외부 상태나 서버 데이터에 의해 업데이트되어야 할 때 유용합니다. `values` 속성은 `defaultValues` 속성을 덮어쓰지만, `useForm`에 `resetOptions: { keepDefaultValues: true }`가 설정되어 있는 경우 덮어쓰지 않습니다. ```javascript // 기본값을 동기적으로 설정 function App({ values }) { useForm({ values, // values 속성이 업데이트될 때 업데이트 }) } function App() { const values = useFetch("/api") useForm({ defaultValues: { firstName: "", lastName: "", }, values, // values가 반환되면 업데이트 }) } ``` #### errors: FieldErrors {#errors} --- `errors` 속성은 변경 사항에 반응하여 서버 에러 상태를 업데이트합니다. 이는 폼이 외부 서버에서 반환된 에러에 의해 업데이트되어야 할 때 유용합니다. ```javascript function App() { const { errors, data } = useFetch("/api") useForm({ errors, // errors가 반환되면 업데이트 }) } ``` #### resetOptions: KeepStateOptions {#resetOptions} --- 이 속성은 값 업데이트 동작과 관련이 있습니다. `values` 나 `defaultValues`가 업데이트될 때, 내부적으로 `reset` API가 호출됩니다. `values` 나 `defaultValues`가 비동기적으로 업데이트된 후 원하는 동작을 지정하는 것이 중요합니다. 이 설정 옵션 자체는 [reset](/docs/useform/reset) 메서드의 옵션을 참조합니다. ```javascript // 기본적으로 비동기 value or defaultValues가 업데이트되면 폼 값이 리셋됩니다. useForm({ values }) useForm({ defaultValues: async () => await fetch() }) // 동작을 설정할 수 있는 옵션들 // eg: 사용자가 상호작용한/변경된 값은 유지하고, 사용자 에러를 제거하지 않기를 원합니다. useForm({ values, resetOptions: { keepDirtyValues: true, // 사용자가 상호작용한 입력은 유지됩니다. keepErrors: true, // 값이 업데이트되더라도 입력 에러는 유지됩니다. }, }) ``` #### context: object {#context} --- | | | | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | | 이 context `객체`는 변경이 가능하며 `resolver`의 두번째 인자 또는 [Yup](https://github.com/jquense/yup) 유효성 검사의 context 객체에 주입됩니다. | | #### criteriaMode: firstError | all {#criteriaMode} --- | | | | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | |
  • `firstError` (기본값)로 설정하면, 각 필드에서 첫 번째 에러만 수집됩니다.
  • `all`로 설정하면, 각 필드의 모든 에러가 수집됩니다.
| | #### shouldFocusError: boolean = true {#shouldFocusError} --- `true` (기본값)로 설정하면, 사용자가 유효성 검사를 통과하지 못한 폼을 제출하는 경우 에러가 있는 첫 번째 필드가 포커스됩니다. - `ref`와 등록(registered)된 필드만 동작합니다. 사용자 정의 등록(registered) 입력은 적용되지 않습니다. 예를 들어: `register('test') // 는 동작하지 않습니다` - 포커스 순서는 `register` 순서에 따라 결정됩니다. #### delayError: number {#delayError} --- | | | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | 이 설정은 에러 상태가 사용자에게 표시되는 것을 지정된 밀리초만큼 지연시킵니다. 만약 사용자가 에러가 발생한 입력을 수정하면 에러는 즉시 제거되며, 지연이 적용되지 않습니다. | | #### shouldUnregister: boolean = false {#shouldUnregister} --- 기본적으로 입력값은 입력이 제거되는 경우에도 유지됩니다. 그러나 `shouldUnregister`를 `true`로 설정하면 언마운트 시 입력을 등록 해제(`unregister`)할 수 있습니다. - 이는 하위 레벨(child-level) 설정을 오버라이드하는 전역 설정입니다. 독립적인 동작을 원한다면, `useForm`이 아닌 컴포넌트나 훅 레벨에서 설정해야 합니다. - 기본적으로 `shouldUnregister: false`의 경우, 언마운트된 필드는 내장된 **유효성 검사를 받지 않습니다**. - `useForm` 레벨에서 `shouldUnregister`를 true로 설정하면 , `defaultValues`는 제출 결과와 합쳐지지 **않습니다**. - `shouldUnregister: true` 로 설정하면 폼이 기본 HTML 폼과 더 유사하게 동작합니다. - 폼 값은 입력 자체에 저장됩니다. - 입력 필드를 언마운트하면 해당 값이 제거됩니다. - 숨겨진 데이터는 `hidden` 속성을 사용하여 저장해야 합니다. - 등록된(registered) 입력만 submission 데이터에 포함됩니다. - hook form이 입력 필드가 DOM에서 언마운트되었는지 확인하려면 언마운트된 입력 필드는 `useForm` 또는 `useWatch`의 `useEffect`에서 알려야 합니다. ````javascript const NotWork = () => { const [show, setShow] = React.useState(false) // ❌ 알림을 받지 못하면, unregister를 호출해야 함. return show && } const Work = ({ control }) => { const { show } = useWatch({ control }) // ✅ useEffect에서 알려줌. return show && } const App = () => { const [show, setShow] = React.useState(false) const { control } = useForm({ shouldUnregister: true }) return (
// ✅ useForm의 useEffect에서 알려줌. {show && } ) } ``` ```` #### shouldUseNativeValidation: boolean = false {#shouldUseNativeValidation} --- 이 설정은 [브라우저의 기본 유효성 검사](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation)를 활성화합니다. 또한 CSS 선택자 `:valid` 및 `:invalid`를 활성화하여 입력 필드의 스타일링을 쉽게 할 수 있습니다. 클라이언트 측 유효성 검사가 비활성화된 경우에도 이러한 선택자를 사용할 수 있습니다. - `reportValidity` 실행은 에러 입력 필드에 포커스를 주기 때문에, 이 기능은 `onSubmit` 및 `onChange` 모드에서만 동작합니다. - 각 등록된(registered) 필드의 유효성 검사 메시지는 브라우저에서 직접 표시되기 위해 문자열이어야 합니다. - 이 기능은 실제 DOM 참조와 연결된 `register` API와  `useController/Controller` 에서만 동작합니다. **Examples:** --- ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm({ shouldUseNativeValidation: true, }) const onSubmit = async (data) => { console.log(data) } return (
) } ``` #### disabled: boolean = false {#disabled} --- 이 설정을 `true`로 지정하면 전체 폼과 해당 폼에 포함된 모든 입력을 비활성화할 수 있습니다. 이는 비동기 작업 중이거나 입력이 일시적으로 반응하지 않아야 하는 기타 상황에서 사용자 상호작용을 방지하는 데 유용합니다. **Examples:** --- ```javascript import { useForm, Controller } from "react-hook-form" const App = () => { const [disabled, setDisabled] = useState(false) const { register, handleSubmit, control } = useForm({ disabled, }) return (
{ setDisabled(true) await sleep(100) setDisabled(false) })} > } name="test" />
) } ``` #### resolver: [Resolver](/ts#Resolver) {#resolver} --- 이 함수는 [Yup](https://github.com/jquense/yup), [Zod](https://github.com/vriad/zod), [Joi](https://github.com/hapijs/joi), [Vest](https://github.com/ealush/vest), [Ajv](https://github.com/ajv-validator/ajv) 등과 같은 외부 유효성 검사 라이브러리를 사용할 수 있게 해줍니다. 목표는 원하는 유효성 검사 라이브러리를 원활하게 통합할 수 있도록 하는 것입니다. 라이브러리를 사용하지 않는 경우에는 언제든 직접 로직을 작성하여 폼의 유효성을 검사할 수 있습니다. ```bash npm install @hookform/resolvers ``` ##### Props --- | Name | Type | Description | | --------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `values` | object | 이 객체에는 전체 폼 값이 포함됩니다. | | `context` | object | 이 객체는 `useForm` 설정에 전달할 수 있는 `context` 객체입니다. 이는 렌더링을 할 때마다 변경될 수 있는 변경 가능한 `객체`입니다. | | `options` |
{JSON.stringify({ criteriaMode: "string", fields: "object", names: "string[]" }, null,2)}
| 이 객체는 `useForm`에서 유효성 검사가 완료된 필드, 필드 이름 및 `criteriaMode`에 대한 정보를 포함하는 옵션 객체입니다. | - 스키마 유효성 검사는 필드 수준의 에러 리포트에 중점을 둡니다. 상위 수준 (Parent-level)의 에러 검사는 바로 위 상위 부모로 제한되며, 그룹 체크박스와 같은 구성 요소에 적용됩니다. - 이 함수는 캐시됩니다. - 사용자가 상호작용을 하는 동안 하나의 필드에서만 입력의 유효성 검사가 다시 이뤄집니다. 라이브러리 자체에서 `에러` 객체를 평가하여 적절하게 리렌더링을 트리거합니다. - resolver는 내장된 유효성 검사 (e.g.: required, min, etc.)와 함께 사용할 수 없습니다. - 사용자 정의 resolver를 만들 때: - 반환하는 객체가 `values`와 `errors` 속성을 모두 포함하도록 해야 합니다. 이들의 기본값은 빈 객체여야 합니다. 예를 들어: `{}`. - `에러` 객체의 키는 필드의 `이름`과 일치해야 하지만, 깊은 에러의 경우 단일 키가 아닌 _계층적 구조여야만_ 합니다: `❌ { "participants.1.name": someErr }`는 제대로 설정되거나 해제되지 않습니다 - 대신 `✅ { participants: [null, { name: someErr } ] }`형태를 사용하세요. 이렇게 하면 `errors.participants[1].name`으로 접근할 수 있습니다 - 평탄한 키를 사용하여 에러를 준비한 다음, zod resolver의 다음 함수를 사용하여 변환할 수 있습니다: [toNestErrors(flatErrs, resolverOptions)](https://github.com/react-hook-form/resolvers/blob/master/src/toNestErrors.ts) **Examples:** --- ```tsx import React from "react" import { useForm } from "react-hook-form" import { yupResolver } from "@hookform/resolvers/yup" import * as yup from "yup" const schema = yup .object() .shape({ name: yup.string().required(), age: yup.number().required(), }) .required() const App = () => { const { register, handleSubmit } = useForm({ resolver: yupResolver(schema), // yup, joi 그리고 직접 작성한 유효성 검사 로직도 사용 가능. }) return (
console.log(d))}>
) } ``` ```tsx import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" const schema = z.object({ name: z.string(), age: z.number(), }) type Schema = z.infer const App = () => { const { register, handleSubmit } = useForm({ resolver: zodResolver(schema), }) const onSubmit = (data: Schema) => { console.log(data) } return (
{ // 입력 처리 console.log(data) })}>
) } ``` ```tsx import { useForm } from "react-hook-form"; import { joiResolver } from "@hookform/resolvers/joi"; import Joi from "joi"; interface IFormInput { name: string age: number } const schema = Joi.object({ name: Joi.string().required(), age: Joi.number().required(), }) const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: joiResolver(schema), }) const onSubmit = (data: IFormInput) => { console.log(data) } return (
); } ``` ```tsx import { useForm } from "react-hook-form" import { ajvResolver } from "@hookform/resolvers/ajv" // must use `minLength: 1` to implement required field const schema = { type: "object", properties: { username: { type: "string", minLength: 1, errorMessage: { minLength: "username field is required" }, }, password: { type: "string", minLength: 1, errorMessage: { minLength: "password field is required" }, }, }, required: ["username", "password"], additionalProperties: false, } const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: ajvResolver(schema), }) return (
console.log(data))}> {errors.username &&

{errors.username.message}

} {errors.password &&

{errors.password.message}

}
) } ``` ```tsx import * as React from "react" import { useForm } from "react-hook-form" import { vestResolver } from "@hookform/resolvers/vest" import vest, { test, enforce } from "vest" const validationSuite = vest.create((data = {}) => { test("username", "Username is required", () => { enforce(data.username).isNotEmpty() }) test("username", "Must be longer than 3 chars", () => { enforce(data.username).longerThan(3) }) test("password", "Password is required", () => { enforce(data.password).isNotEmpty() }) test("password", "Password must be at least 5 chars", () => { enforce(data.password).longerThanOrEquals(5) }) test("password", "Password must contain a digit", () => { enforce(data.password).matches(/[0-9]/) }) test("password", "Password must contain a symbol", () => { enforce(data.password).matches(/[^A-Za-z0-9]/) }) }) const App = () => { const { register, handleSubmit } = useForm({ resolver: vestResolver(validationSuite), }) return (
console.log(data))}>
) } ``` ```tsx import * as React from "react" import { useForm } from "react-hook-form" import * as Joi from "joi" interface IFormInputs { username: string } const validationSchema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), }) const App = () => { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: async (data) => { const { error, value: values } = validationSchema.validate(data, { abortEarly: false, }) return { values: error ? {} : values, errors: error ? error.details.reduce((previous, currentError) => { return { ...previous, [currentError.path[0]]: currentError, } }, {}) : {}, } }, }) const onSubmit = (data: IFormInputs) => console.log(data) return (

resolver

{errors.username &&

errors.username.message

}
) } ``` 더 필요하신가요? [Resolver Documentation](https://github.com/react-hook-form/resolvers#quickstart)를 확인하세요. 다음 코드 스니펫을 통해 스키마를 디버그할 수 있습니다: ```javascript resolver: async (data, context, options) => { // 여기에서 유효성 검사 스키마를 디버그할 수 있습니다. console.log("formData", data) console.log( "validation result", await anyResolver(schema)(data, context, options) ) return anyResolver(schema)(data, context, options) } ``` #### `useForm` return and `useEffect` dependencies 앞으로의 주요 릴리스에서, 성능 최적화와 `formState`의 변경 사항 반영을 위해 `useForm`의 반환값이 메모이제이션될 예정입니다. 따라서, `useEffect`의 의존성 배열에 `useForm`의 전체 반환값을 추가하면 무한 루프가 발생할 수 있습니다. 다음 코드는 이러한 상황을 발생시킬 가능성이 있습니다: ```javascript const methods = useForm() useEffect(() => { methods.reset({ ... }) }, [methods]) ``` 아래와 같이 관련된 메서드만 전달하면 이러한 종류의 문제를 피할 수 있습니다: ```javascript const methods = useForm() useEffect(() => { methods.reset({ ... }) }, [methods.reset]) ``` 권장하는 방법은 구조 분해한 메서드를 `useEffect`의 의존성 배열에 전달하는 것입니다. ```javascript const { reset } = useForm() useEffect(() => { reset({ ... }) }, [reset]) ``` [더 자세한 정보는 이 이슈에서 확인할 수 있습니다](https://github.com/react-hook-form/react-hook-form/issues/12463) #### Return --- 다음 목록에는 `useForm`이 반환하는 속성에 대한 레퍼런스가 포함되어 있습니다. - [register](/docs/useform/register) - [unregister](/docs/useform/unregister) - [formState](/docs/useform/formstate) - [watch](/docs/useform/watch) - [handleSubmit](/docs/useform/handlesubmit) - [reset](/docs/useform/reset) - [resetField](/docs/useform/resetfield) - [setError](/docs/useform/seterror) - [clearErrors](/docs/useform/clearerrors) - [setValue](/docs/useform/setvalue) - [setFocus](/docs/useform/setfocus) - [getValues](/docs/useform/getvalues) - [getFieldState](/docs/useform/getfieldstate) - [trigger](/docs/useform/trigger) - [control](/docs/useform/control) - [Form](/docs/useform/form) --- # register > 비제어/제어 입력 필드 등록(register) ## \ `register:` `(name: string, options?: RegisterOptions) => ({ ref, name, onChange, onBlur })` 이 메서드를 사용하면 입력 또는 선택 요소를 등록(register)하고 React Hook Form에 유효성 검사 규칙을 적용할 수 있습니다. 유효성 검사 규칙은 모두 HTML 표준을 기반으로 하며 사용자 정의 유효성 검사 메서드도 허용합니다. ### Props --- | Name | Type | Description | | --------- | ------------------------------------ | ----------------- | | `name` | string | 입력 필드의 이름. | | `options` | RegisterOptions | 입력 필드의 동작. | ### Return --- | Name | Type | Description | | ---------- | ---------------------------------- | ------------------------------------------------------------------ | | `ref` | React.ref | hook form을 입력 필드에 연결하기 위해 사용하는 React 엘리먼트 ref. | | `name` | string | 등록(registered)된 입력의 이름. | | `onChange` | ChangeHandler | `onChange` 속성은 입력 변경 이벤트를 구독합니다. | | `onBlur` | ChangeHandler | `onBlur` 속성은 입력 블러(blur) 이벤트를 구독합니다. | 제출된 값은 다음과 같은 형태로 표시됩니다: | Input Name | Submit Result | | ------------------------------------------------- | --------------------------------------------------------- | | register("firstName") | `{ firstName: value }` | | register("name.firstName") | `{ name: { firstName: value } }` | | register("name.firstName.0") | `{ name: { firstName: [ value ] } }` | ### Options --- By selecting the register option, the API table below will get updated. | Name | Description | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ref`React.Ref | React 엘리먼트 `ref` | | `required`boolean | 폼을 제출하기 전에 해당 입력 필드에 값이 있어야 함을 나타냅니다. **참고:** 이 구성은 필수 입력 유효성 검사를 위한 웹 제한 API와 일치합니다. 객체나 배열 유형의 입력인 경우 validate 함수를 대신 사용하세요. | | `maxLength`number | 이 입력에서 허용할 값의 최대 길이. | | `minLength`number | 이 입력에서 허용할 값의 최소 길이. | | `max`number | 이 입력에서 허용할 최대값. | | `min`number | 이 입력에서 허용할 최소값. | | `pattern`RegExp | 입력에 대한 정규식 패턴. **참고:** `/g` 플래그를 가진 `RegExp` 객체는 일치가 발생한 마지막 인덱스를 추적합니다. | | `validate`Function \| `Record` | Validate 함수는 required 속성에 포함된 다른 유효성 검사 규칙에 의존하지 않고 독립적으로 실행됩니다. **참고:** 다른 규칙들은 주로 문자열, 문자열 배열(string[]), 숫자 및 boolean 데이터 타입에 적용되기 때문에 객체 또는 배열 입력 데이터의 경우 유효성 검사에 validate 함수를 사용하는 것이 권장됩니다. | | `valueAsNumber`boolean | 보통 숫자를 반환합니다. 문제가 발생하면 `NaN` 이 반환됩니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다.
  • 숫자 입력 필드에만 적용되며, 데이더 조작 없이 동작합니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `valueAsDate`boolean | 보통 `Date`객체를 반환합니다. 문제가 발생하면 `Invalid Date`가 반환됩니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다.
  • 입력 필드에만 적용됩니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `setValueAs`\(value: any) => T | 함수를 실행하여 입력값을 반환합니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다. 또한, `valueAsNumber`나 `valueAsDate`가 true인 경우 `setValueAs`는 무시됩니다.
  • 텍스트 입력 필드에만 적용됩니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `disabled`boolean = false | `disabled`를 `true`로 설정하면 입력 값이 `undefined`가 되며 입력 컨트롤이 비활성화됩니다.
  • `disabled` 속성은 내장된 유효성 검사 규칙도 제외합니다.
  • 스키마 유효성 검사를 위해, 입력 또는 컨텍스트 객체에서 반환된 `undefined` 값을 활용할 수 있습니다.
| | `onChange`(e: SyntheticEvent) => void | 변경 이벤트에서 호출될 `onChange` 함수. | | `onBlur`(e: SyntheticEvent) => void | 블러(blur) 이벤트에서 호출될 `onBlur` 함수. | | `value`unknown | 등록된(registered) 입력의 값을 설정합니다. 이 속성은 `useEffect` 내부에서 사용하거나 한 번만 호출해야 하며, 재실행할 때마다 입력 값을 업데이트하거나 덮어씁니다. | | `shouldUnregister`boolean | 입력이 언마운트된 후 등록이 해제되며 `defaultValues`도 제거됩니다. **참고:** 이 속성은 입력이 언마운트/리마운트 및 재정렬된 후 `unregister` 함수가 호출되므로 `useFieldArray`와 함께 사용할 때 피해야 합니다. | | `deps`string \| string[] | 유효성 검사는 종속 입력에 대해 트리거 됩니다. 이는 trigger가 아닌 register api 에만 제한됩니다. | | Name | Description | | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ref`React.Ref | React 엘리먼트 `ref` | | `required`string \| | 폼을 제출하기 전에 해당 입력 필드에 값이 있어야 함을 나타냅니다. **참고:** 이 구성은 필수 입력 유효성 검사를 위한 웹 제한 API와 일치합니다. 객체나 배열 유형의 입력인 경우 validate 함수를 대신 사용하세요. | | `maxLength` | 이 입력에서 허용할 값의 최대 길이. | | `minLength` | 이 입력에서 허용할 값의 최소 길이. | | `max` | 이 입력에서 허용할 최대값. | | `min` | 이 입력에서 허용할 최소값. | | `pattern` | 입력에 대한 정규식 패턴. **참고:** `/g` 플래그를 가진 `RegExp` 객체는 일치가 발생한 마지막 인덱스를 추적합니다. | | `validate`Function \| `Record` | Validate 함수는 required 속성에 포함된 다른 유효성 검사 규칙에 의존하지 않고 독립적으로 실행됩니다. **참고:** 다른 규칙들은 주로 문자열, 문자열 배열(string[]), 숫자 및 boolean 데이터 타입에 적용되기 때문에 객체 또는 배열 입력 데이터의 경우 유효성 검사에 validate 함수를 사용하는 것이 권장됩니다. | | `valueAsNumber`boolean | 보통 숫자를 반환합니다. 문제가 발생하면 `NaN` 이 반환됩니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다.
  • 숫자 입력 필드에만 적용되며, 데이더 조작 없이 동작합니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `valueAsDate`boolean | 보통 `Date`객체를 반환합니다. 문제가 발생하면 `Invalid Date`가 반환됩니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다.
  • 입력 필드에만 적용됩니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `setValueAs`\(value: any) => T | 함수를 실행하여 입력값을 반환합니다.
  • `valueAs` 프로세스는 유효성 검사 **전**에 발생합니다. 또한, `valueAsNumber`나 `valueAsDate`가 true인 경우 `setValueAs`는 무시됩니다.
  • 텍스트 입력 필드에만 적용됩니다.
  • `defaultValue`나 `defaultValues`는 변환되지 않습니다.
| | `disabled`boolean = false | `disabled`를 `true`로 설정하면 입력 값이 `undefined`가 되며 입력 컨트롤이 비활성화됩니다.
  • `disabled` 속성은 내장된 유효성 검사 규칙도 제외합니다.
  • 스키마 유효성 검사를 위해, 입력 또는 컨텍스트 객체에서 반환된 `undefined` 값을 활용할 수 있습니다.
| | `onChange`(e: SyntheticEvent) => void | 변경 이벤트에서 호출될 `onChange` 함수. | | `onBlur`(e: SyntheticEvent) => void | 블러(blur) 이벤트에서 호출될 `onBlur` 함수. | | `value`unknown | 등록된(registered) 입력의 값을 설정합니다. 이 속성은 `useEffect` 내부에서 사용하거나 한 번만 호출해야 하며, 재실행할 때마다 입력 값을 업데이트하거나 덮어씁니다. | | `shouldUnregister`boolean | 입력이 언마운트된 후 등록이 해제되며 `defaultValues`도 제거됩니다. **참고:** 이 속성은 입력이 언마운트/리마운트 및 재정렬된 후 `unregister` 함수가 호출되므로 `useFieldArray`와 함께 사용할 때 피해야 합니다. | | `deps`string \| string[] | 유효성 검사는 종속 입력에 대해 트리거 됩니다. 이는 trigger가 아닌 register api 에만 제한됩니다. |
  • 이름은 **필수**이며 **고유**해야 합니다(기본 라디오 및 체크박스 제외). 입력 이름은 점과 대괄호 구문을 모두 지원하므로 중첩된 폼 필드를 쉽게 만들 수 있습니다.
  • 이름은 은 숫자로 시작하거나 키 이름으로 숫자를 사용할 수 없습니다. 특수 문자의 사용도 피해야 합니다.
  • TypeScript 사용 시 일관성을 위해 점 구문만을 사용하고 있으므로, 배열 폼 값에는 대괄호 `[]` 를 사용할 수 없습니다. ```javascript register('test.0.firstName'); // ✅ register('test[0]firstName'); // ❌ ```
  • 비활성화된 입력은 undefined 폼 값을 결과로 제공합니다. 사용자가 입력을 업데이트하지 못하도록 하려면 `readOnly`를 사용하거나 전체 `fieldset`을 비활성화할 수 있습니다. 다음은 [예제](https://codesandbox.io/s/react-hook-form-disabled-inputs-oihxx)입니다.
  • 필드 배열을 생성하려면 입력 이름 뒤에 점과 숫자를 붙여야 합니다. 예: `test.0.data`
  • 렌더링할 때마다 이름을 변경하면 새로운 입력이 등록(registered)됩니다. 따라서 각 등록된(registered) 입력에 대해 고정된 이름을 사용하는 것이 좋습니다.
  • 언마운트에 따라 입력 값과 참조는 더 이상 제거되지 않습니다. 해당 값과 참조를 제거하려면 unregister를 호출할 수 있습니다.
  • 개별 register 옵션은 `undefined`나 `{}`로 제거할 수 없습니다. 대신 개별 속성을 업데이트할 수 있습니다. ```javascript register('test', { required: true }); register('test', {}); // ❌ register('test', undefined); // ❌ register('test', { required: false }); // ✅ ```
  • 타입 검사와 충돌을 피하기 위해 `ref`, `_f`와 같은 키워드는 피해야 합니다.
**Examples** --- **Register input or select** ```javascript sandbox="https://codesandbox.io/s/register-is0sfo" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm({ defaultValues: { firstName: "", lastName: "", category: "", checkbox: [], radio: "", }, }) return (
) } ``` **Custom async validation** ```javascript import { useForm } from "react-hook-form" import { checkProduct } from "./service" export default function App() { const { register, handleSubmit } = useForm() return (
{ if (!category) return "Choose a category" if (!product) return "Specify your product" const isInStock = await checkProduct(category, product) return isInStock || "There is no such product" }, }, })} />
) } ``` ### Video --- ### Tips --- #### 구조 분해 할당 ```javascript const { onChange, onBlur, name, ref } = register('firstName'); // 지정한 이름에 해당하는 필드 경로에 대해 타입 검사를 포함합니다. // 위와 같음 ``` #### 커스텀 Register `useEffect`를 사용해 입력 필드를 등록하고 가상 입력(virtual inputs)으로 취급할 수도 있습니다. 제어 컴포넌트를 위해, 이 과정을 대신 처리해주는 커스텀 훅인 [useController](/docs/usecontroller)와 [Controller](/docs/usecontroller/controller) 컴포넌트를 제공합니다. 필드를 수동으로 등록(register)한다면, [setValue](/docs/useform/setvalue)를 사용하여 입력 값을 직접 업데이트해야 합니다. ```javascript register('firstName', { required: true, min: 8 }); setValue('lastChange', value))} /> ``` #### `innerRef`, `inputRef`로 어떻게 작업할까? 커스텀 입력 컴포넌트가 ref를 올바르게 노출하지 않을 때는, 아래와 같은 방식으로 동작하도록 만들 수 있습니다. ```javascript // 동작하지 않음. ref가 할당되지 않았기 때문 const firstName = register('firstName', { required: true }) ``` --- # unregister > 비제어, 제어 입력 등록 해제(Unregister) ## \ `unregister:` (name: string | string[], options) => void 이 메서드를 사용하면 단일 입력 또는 입력 배열을 등록 해제(`unregister`) 할 수 있습니다. 또한 입력을 등록 해제(unregister)한 후 상태를 유지하기 위한 두 번째 선택적 인수를 제공합니다. ### Props --- 아래 예시는 `unregister` 메서드를 호출할 때 예상되는 상황을 보여줍니다. ```javascript ``` | Type | Input Name | Value | | ----------------------------- | -------------------------------------- | ------------------ | | string | `unregister("yourDetails")` | `{}` | | string | `unregister("yourDetails.firstName")` | `{ lastName: '' }` | | string[] | `unregister(["yourDetails.lastName"])` | `''` | ### Options --- | Name | Type | Description | | ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `keepDirty` | boolean | 이 작업을 수행하는 동안 `isDirty`와 `dirtyFields`는 유지됩니다. 그러나 `isDirty`는 `defaultValues`에 대해 측정되기 때문에 다음 사용자 입력이 `isDirty` formState를 업데이트하지 않는다고 보장하지는 않습니다. | | `keepTouched` | boolean | `touchedFields`는 등록을 해제(unregister)한 후 더 이상 해당 입력을 제거하지 않습니다. | | `keepIsValid` | boolean | 이 작업 중에는 `isValid`가 유지됩니다. 그러나 다음 사용자 입력이 스키마 유효성 검사를 위해 `isValid`를 업데이트하지 않는다고 보장하지는 않으므로 등록 해제(unregister)에 따라 스키마를 조정해야 합니다. | | `keepError` | boolean | `errors`가 업데이트되지 않습니다. | | `keepValue` | boolean | 입력의 현재 `value`는 업데이트되지 않습니다. | | `keepDefaultValue` | boolean | `useForm`에 정의된 입력의 `defaultValue`는 그대로 유지됩니다. | - 이 방법을 사용하면 입력의 참조와 해당 값이 제거되므로 **내장 유효성 검사** 규칙도 제거됩니다. - 입력을 등록 해제(unregister)하면 스키마 유효성 검사에 영향을 미치지 않습니다. ```javascript const schema = yup .object() .shape({ firstName: yup.string().required(), }) .required() unregister("firstName") // 이 경우 firstName input에 대한 유효성 검사는 제거되지 않습니다. ``` - `register` 콜백이 있는 입력은 반드시 unmount해야 하며, 그렇지 않으면 입력이 다시 등록(register)됩니다. ```javascript const [show, setShow] = React.useState(true) const onClick = () => { unregister("test") setShow(false) // register가 다시 호출되지 않도록 해당 입력을 unmount해야 합니다. } { show && } ``` **예제:** --- ```tsx import React, { useEffect } from "react" import { useForm } from "react-hook-form" interface IFormInputs { firstName: string lastName?: string } export default function App() { const { register, handleSubmit, unregister } = useForm() const onSubmit = (data: IFormInputs) => console.log(data) React.useEffect(() => { register("lastName") }, [register]) return (
) } ``` ```javascript import React, { useEffect } from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, unregister } = useForm() React.useEffect(() => { register("lastName") }, [register]) return (
) } ``` ### Video --- --- # formState > 폼의 상태 ## \ `formState:` `Object` 이 객체는 전체 폼 상태에 대한 정보를 포함하고 있습니다. 폼 애플리케이션과 사용자의 상호작용을 추적하는 데 도움이 됩니다. ### Return --- | Name | Type | Description | | -------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `isDirty` | boolean | 사용자가 입력 중 하나라도 수정한다면 `true`로 설정됩니다.
  • **중요:** 모든 입력의 `defaultValues`을 `useForm`에 제공해야 hook form이 폼이 변경되었는지 비교할 수 있는 단일 소스를 가질 수 있습니다.
  • 파일 타입 입력은 파일 선택 취소 및 [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) 객체 관리 때문에 애플리케이션 수준에서 관리되어야 합니다.
  • 사용자 정의 객체, 클래스 또는 파일 객체는 지원하지 않습니다.
| | `dirtyFields` | object | 사용자가 수정한 필드를 포함하는 객체입니다. 라이브러리가 `defaultValues`와 비교할 수 있도록 `useForm`을 통해 모든 입력의 `defaultValues`를 제공해야 합니다.
  • **중요:** `useForm`에서 `defaultValues`를 제공하여, hook form이 각 필드의 변경 상태를 비교할 수 있는 단일 소스를 가질 수 있도록 해야합니다.
  • Dirty 필드는 전체 폼이 아닌 개별 필드 수준에서 dirty로 표시되므로, Dirty 필드는 폼이 `isDirty` 상태임을 나타내지 **않습니다**. 전체 폼 상태를 확인하려면 `isDirty`를 사용하세요.
| | `touchedFields` | object | 사용자가 상호작용한 모든 입력을 포함하는 객체입니다. | | `defaultValues` | object | [useForm](/docs/useform)의 `defaultValues`에 설정된 값 또는 [reset](/docs/useform/reset) API를 통해 업데이트된 `defaultValues`입니다. | | `isSubmitted` | boolean | 폼이 제출된 후 `true`로 설정됩니다. `reset` 메서드가 호출될 때까지 `true`를 유지합니다. | | `isSubmitSuccessful` | boolean | 런타임 에러 없이 폼이 성공적으로 제출되었음을 나타냅니다. | | `isSubmitting` | boolean | 폼이 현재 제출 중이면 `true`, 그렇지 않으면 `false` 입니다. | | `isLoading` | boolean | 비동기 기본 값을 로드 중인 경우 `true` 입니다.
  • **중요:** 이 속성은 비동기 `defaultValues`에만 적용됩니다. await fetch('/api')\n})`}/>
| | `submitCount` | number | 폼이 제출된 횟수입니다. | | `isValid` | boolean | 폼에 에러가 없으면 `true`로 설정됩니다.
  • `setError` has no effect on `isValid` `formState`, `isValid` will always derived via the entire form validation result.
| | `isValidating` | boolean | 유효성 검사 중 `true`로 설정됩니다. | | `validatingFields` | object | 비동기 유효성 검사가 이뤄지는 필드를 캡쳐합니다. | | `errors` | object | 필드 에러가 포함된 객체입니다. 에러 메세지를 쉽게 가져오기 위해 [ErrorMessage](/docs/useformstate/errormessage)도 있습니다. | | `disabled` | boolean | [useForm](/docs/useform)의 disabled prop을 통해 폼이 비활성화된 경우 true로 설정됩니다. | | `isReady` | boolean | `formState` 구독 설정이 준비되면 `true`로 설정됩니다.
  • 부모 컴포넌트가 설정을 완료하기 전에 자식 컴포넌트가 렌더링됩니다. 구독이 준비되기 전에 자식 컴포넌트에서 `useForm` 메서드(예: `setValue`)를 사용하면 문제가 발생할 수 있습니다. `isReady` 플래그를 사용하여 자식 컴포넌트에서 상태를 업데이트하기 전에 폼이 초기화되었는지 확인하세요.

    setValue('test', 'data'), []) \n\n// Children component: ✅ \nuseEffect(() => isReady && setValue('test', 'data'), [isReady])`}/>
|
  • `formState` 는 렌더링 성능을 향상시키고 특정 상태가 구독되지 않았을 때 추가 로직을 건너뛰기 위해 [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 로 감싸져 있습니다. 따라서 상태 업데이트를 활성화하려면 `렌더링` 전에 formState을 호출하거나 읽어야 합니다.
  • `formState`는 일괄적으로 업데이트됩니다. `useEffect`를 통해 `formState`를 구독하려면, `formState`를 배열(optional array)에 포함시켜야 합니다. ```javascript useEffect(() => { if (formState.errors.firstName) { // 여기에 로직을 작성하세요. } }, [formState]) // ✅ // ❌ [formState.errors]는 useEffect를 트리거하지 않습니다. ```
  • Pay attention to the logical operator when subscription to `formState`. ; // ✅ 변경 사항을 구독하려면 모든 formState 값을 읽으세요. const { isDirty, isValid } = formState; return
**Examples** --- ### Video --- --- # watch > 입력 변경 사항 구독 ## \ `watch:` _UseFormWatch_ 이 메서드는 지정된 입력을 감시하고 해당 값을 반환합니다. 입력 값을 렌더링하고 조건에 따라 렌더링할 내용을 결정할 때 유용합니다. ### Overloads 이 함수는 주로 **두 가지 목적**으로 사용됩니다: - `watch(name: string, defaultValue?): unknown` - `watch(names: string[], defaultValue?): {[key:string]: unknown}` - `watch(): {[key:string]: unknown}` 이 네 가지 오버로드 각각에 대한 설명은 아래와 같습니다. #### 1-a. 단일 필드 감시하기 `watch(name: string, defaultValue?: unknown): unknown` --- 렌더링 외부에서 사용되는 단일 필드를 감시하고 구독합니다. **Params** | Name | Type | Description | | -------------- | ------------------------------ | --------------------------- | | `name` | `string` | 감시할 필드의 이름 | | `defaultValue` | `unknown` | _선택 사항_. 필드의 기본 값 | **반환** 단일 필드의 값을 반환합니다. ```tsx const name = watch("name") ``` #### 1-b. 여러 필드 감시하기 `watch(names: string[], defaultValue?: {[key:string]: unknown}): unknown[]` --- 렌더링 바깥에서 사용되는 여러 필드를 감시하고 구독합니다. **Params** | Name | Type | Description | | -------------- | ---------------------------------------------- | -------------------------- | | `names` | `string[]` | 감시할 필드의 이름들 | | `defaultValue` | `{[key:string]: unknown}` | _선택 사항_. 필드의 기본값 | **반환** 필드 값의 배열을 반환합니다. ```tsx const [name, name1] = watch(["name", "name1"]) ``` #### 1-c. 전체 폼 감시하기 `watch(): {[key:string]: unknown}` --- onChange 이벤트를 기반으로 전체 폼의 업데이트 및 변경 사항을 감시하고 구독하며, useForm**에서 리렌더링을 수행합니다.** **Params** 없음 **반환**\*\*\***\* 전체 폼 값을 반환합니다.**\*\*\*\* ```tsx const formValues = watch() ``` #### 2. Deprecated: [subscribe](/docs/useform/subscribe) 사용 또는 마이그레이션을 고려해 주세요. 콜백 함수와 함께 감시하기 `watch(callback: (data, { name, type }) => void, defaultValues?: {[key:string]: unknown}): { unsubscribe: () => void }` --- 리렌더링을 트리거하지 않고 필드 업데이트/변경 사항을 구독합니다. **Params** | Name | Type | Description | | --------------- | ----------------------------------------------------- | ----------------------------------------------- | | `callback` | `(data, { name, type }) => void` | 모든 필드의 변경 사항을 구독하기 위한 콜백 함수 | | `defaultValues` | `{[key:string]: unknown}` | _선택 사항_. 폼 전체의 기본값 | **반환** `unsubscribe` 함수를 가진 객체. ```tsx useEffect(() => { const { unsubscribe } = watch((value) => { console.log(value) }) return () => unsubscribe() }, [watch]) ``` ### Rules --- - `defaultValue`가 정의되지 않은 경우, `watch`의 첫 번째 렌더링은 `register` 이전에 호출되기 때문에 `undefined`를 반환합니다. 이 동작을 방지하려면 `useForm`에서 `defaultValues`를 제공하는 것이 좋지만 인라인 `defaultValue`를 두 번째 인수로 설정할 수 있습니다. - `defaultValue`와 `defaultValues`를 모두 제공되면 `defaultValue`가 반환됩니다. - 이 API는 앱 또는 form의 루트에서 다시 렌더링을 트리거하므로 성능 문제가 발생하는 경우 콜백 또는 [useWatch](/docs/usewatch) API를 사용하는 것이 좋습니다. - `watch` 결과는 값 업데이트를 감지하기 위해 `useEffect`의 deps 대신 렌더 단계에 최적화되어 있으므로, 값 비교를 위해 외부 커스텀 훅을 사용하는 것이 좋습니다. ### Examples: --- #### 폼에서의 Watch ```tsx import { useForm } from "react-hook-form" interface IFormInputs { name: string showAge: boolean age: number } function App() { const { register, watch, formState: { errors }, handleSubmit, } = useForm() const watchShowAge = watch("showAge", false) // 두 번째 인수로 기본 값을 제공할 수 있습니다. const watchAllFields = watch() // 인수를 전달하지 않으면, 모든 것을 감시하게 됩니다. const watchFields = watch(["showAge", "age"]) // 이름을 통해 특정 필드를 대상으로 할 수도 있습니다. const onSubmit = (data: IFormInputs) => console.log(data) return ( <>
{/*예(yes)를 선택하면 나이 입력란을 표시합니다*/} {watchShowAge && ( )}
) } ``` ```javascript import { useForm } from "react-hook-form" function App() { const { register, watch, formState: { errors }, handleSubmit, } = useForm() const watchShowAge = watch("showAge", false) // 두 번째 인수로 기본 값을 제공할 수 있습니다. const watchAllFields = watch() // 인수를 전달하지 않으면, 모든 것을 감시하게 됩니다. const watchFields = watch(["showAge", "number"]) // 이름을 통해 특정 필드를 대상으로 할 수도 있습니다. const onSubmit = (data) => console.log(data) return ( <>
{/* ‘예’ 선택에 따라 나이 input 필드를 표시 */} {watchShowAge && ( )}
) } ``` #### 필드 배열에서의 Watch ```tsx import * as React from "react" import { useForm, useFieldArray } from "react-hook-form" type FormValues = { test: { firstName: string lastName: string }[] } function App() { const { register, control, handleSubmit, watch } = useForm() const { fields, remove, append } = useFieldArray({ name: "test", control, }) const onSubmit = (data: FormValues) => console.log(data) console.log(watch("test")) return (
{fields.map((field, index) => { return (
) })}
) } ``` ```javascript import * as React from "react" import { useForm, useFieldArray } from "react-hook-form" function App() { const { register, control, handleSubmit, watch } = useForm() const { fields, remove, append } = useFieldArray({ name: "test", control, }) const onSubmit = (data) => console.log(data) console.log(watch("test")) return (
{fields.map((field, index) => { return (
) })}
) } ``` ## Video --- --- # subscribe > 리렌더링 없이 폼 상태 업데이트 구독 ## `subscribe:` `UseFormSubscribe` [`formState`](/docs/useform/formState) 변경사항과 값 업데이트를 구독합니다. 개별 필드나 전체 폼을 구독할 수 있으며, 폼 변경으로 인한 불필요한 리렌더링을 방지할 수 있습니다. ### Props --- | Name | Type | Description | Example | | --------- | --------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | undefined | 전체 폼을 구독합니다. | `subscribe()` | | | string[] | **name**으로 여러 필드를 구독합니다. | `subscribe({ name: ['firstName', 'lastName'] })` | | formState | `Partial` | 구독할 [`formState`](/docs/useform/formState)를 선택합니다. | | | callback | `Function` | 구독을 위한 콜백 함수입니다. | { \n console.log(values) \n } \n})`}/> | | exact | boolean | 구독 시, 정확히 일치하는 매칭을 활성화합니다. | `subscribe({ name: 'target', exact: true })` |
  • 이 함수는 변경사항 구독만을 위한 것이며, 상태 업데이트 dispatch나 리렌더링 트리거는 허용되지 않습니다. 예: `setValue` 또는 `reset`

  • 이 함수는 createFormControl.subscribe와 동일한 기능을 공유합니다. 다만 [createFormControl](/docs/createFormControl)은 React 컴포넌트 외부에서도 초기화할 수 있다는 점이 다릅니다.

  • 이 함수는 **렌더링** 없이 폼 상태를 구독하기 위한 전용 함수입니다. [watch](/docs/useform/watch) 콜백 함수 대신 이 함수를 사용하세요.

**예시:** --- ```tsx import { useForm } from "react-hook-form" type FormInputs = { firstName: string lastName: string } export default function App() { const { register, subscribe } = useForm() useEffect(() => { // 구독 해제를 반드시 해야 합니다; const callback = subscribe({ formState: { values: true, }, callback: ({ values }) => { console.log(values) }, }) return () => callback() // subscribe를 직접 반환할 수도 있습니다 // return subscribe(); }, [subscribe]) return (
) } ``` --- # handleSubmit > 서버로 전송 준비 ## \ `handleSubmit:` `((data: Object, e?: Event) => Promise, (errors: Object, e?: Event) => Promise) => Promise` 이 함수는 폼 유효성 검사에 성공하면, 폼 데이터를 받게 됩니다. ### Props --- | Name | Type | Description | | ------------------ | ------------------------------------------------------------------- | ----------- | | SubmitHandler | `(data: Object, e?: Event) => Promise` | 성공 콜백. | | SubmitErrorHandler | `(errors: Object, e?: Event) => Promise` | 에러 콜백. | - handleSubmit을 사용하면 폼을 비동기적으로 쉽게 제출할 수 있습니다. ```javascript copy handleSubmit(onSubmit)() // 비동기 유효성 검사를 위해 async 함수를 전달할 수 있습니다. handleSubmit(async (data) => await fetchAPI(data)) ``` - `disabled`된 입력은 폼 값에서 `undefined`로 나타납니다. 입력을 사용자가 수정하지 못하게 하면서 값을 유지하려면, `readOnly`를 사용하거나 전체 <fieldset />을 disabled할 수 있습니다. [예시](https://codesandbox.io/s/react-hook-form-disabled-inputs-oihxx)를 참고하세요. - `handleSubmit` 함수는 onSubmit 콜백 내부에서 발생한 에러를 처리하지 않으므로, 비동기 요청에서 에러를 처리할 때는 try-catch를 사용하여 유저에게 에러를 친절하게 처리해 주는 것을 권장합니다. ```javascript const onSubmit = async () => { // 에러가 발생할 수 있는 비동기 요청 try { // 비동기 요청 응답 } catch (e) { // 에러 처리 } } return
``` **Examples:** --- **동기** ```tsx import React from "react" import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form" type FormValues = { firstName: string lastName: string email: string } export default function App() { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) const onError: SubmitErrorHandler = (errors) => console.log(errors) return ( ) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm() const onSubmit = (data, e) => console.log(data, e) const onError = (errors, e) => console.log(errors, e) return (
) } ``` **비동기** ```javascript import { useForm } from "react-hook-form"; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); function App() { const { register, handleSubmit, formState: { errors } } = useForm(); const onSubmit = async data => { await sleep(2000); if (data.username === "bill") { alert(JSON.stringify(data)); } else { alert("There is an error"); } }; return (
); } ``` ### Video --- 다음 동영상 튜토리얼에서는 `handleSubmit` API에 대해 자세히 설명합니다. --- # reset > 폼 상태와 값을 초기화 ## \ `reset:` `(values?: T | ResetAction, options?: Record) => void` 전체 폼 상태, 필드 참조 및 구독을 초기화합니다. 선택적 인자가 있으며, 부분적인 폼 상태 초기화를 허용할 수 있습니다. ### Props --- `Reset`은 formState를 유지하는 옵션을 제공합니다. 다음 옵션을 사용할 수 있습니다: | Name | | Type | Description | | --------- | ------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `values` | | object \| (values: Object) => Object | 폼 값을 초기화할 선택적 객체로, **전체** defaultValues를 제공하는 것을 권장합니다. | | `options` | `keepErrors` | boolean | 모든 에러가 유지됩니다. 하지만 이후 사용자 동작에 의해 보장되지 않을 수 있습니다. | | | `keepDirty` | boolean | `DirtyFields` 폼 상태가 유지되며, `isDirty` 는 이후 사용자 동작이 있을 때까지 일시적으로 현재 상태로 유지됩니다. **중요:** 이 옵션은 폼 입력 값을 반영하지 않고 오직 dirty fields 폼 상태만 반영합니다. | | | `keepDirtyValues` | boolean | `DirtyFields`와 `isDirty`가 유지되며, 더티 상태가 아닌 필드만 최신 초기화 값으로 업데이트됩니다. [예시 확인하기.](https://codesandbox.io/s/react-keepdirtyvalues-o8to91) **중요:** formState `dirtyFields`를 구독해야 합니다. | | | `keepValues` | boolean | 폼 입력 값이 변경되지 않습니다. | | | `keepDefaultValues` | boolean | `useForm`을 통해 초기화된 동일한 defaultValues를 유지합니다.
  • `isDirty`는 다시 업데이트됩니다: 이는 제공된 새로운 값과 원래 `defaultValues`의 비교 결과로 설정됩니다.
  • 값이 제공되면 dirtyFields가 다시 업데이트됩니다: 이는 제공된 새로운 값과 원래 defaultValues 간의 비교 결과로 설정됩니다.
| | | `keepIsSubmitted` | boolean | `isSubmitted` 상태가 변경되지 않습니다. | | | `keepTouched` | boolean | `isTouched` 상태가 변경되지 않습니다. | | | `keepIsValid` | boolean | `isValid` 상태가 이후 사용자 동작이 있을 때까지 현재 상태로 일시적으로 유지됩니다. | | | `keepSubmitCount` | boolean | `submitCount` 상태가 변경되지 않습니다. | - 제어 컴포넌트의 경우 `defaultValues`를 `useForm`에 전달해야 `Controller` 컴포넌트의 값을 `reset`할 수 있습니다. - `reset` API에 `defaultValues`가 제공되지 않으면, HTML 기본 [reset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset) API가 호출되어 폼이 복원됩니다. - `useForm`의 `useEffect`가 호출되기 전에 `reset`을 호출하는 것을 피하세요. 이는 `useForm`의 구독이 준비된 후에만 `reset`이 신호를 보내 폼 상태 업데이트를 flush할 수 있기 때문입니다. - submission 후 `useEffect` 내부에서 `reset`을 호출하는 것이 좋습니다. ```javascript useEffect(() => { reset({ data: "test", }) }, [isSubmitSuccessful]) ``` - `defaultValues`를 useForm에 제공한 경우, 인자 없이 `reset`을 실행하는 것도 가능합니다. ```javascript reset() // 폼을 기본값으로 다시 업데이트 reset({ test: "test" }) // 기본값과 폼 값을 업데이트 reset(undefined, { keepDirtyValues: true }) // 다른 폼 상태를 초기화하지만 기본값과 폼 값을 유지 ``` **Examples:** --- **비제어** ```tsx import { useForm } from "react-hook-form" interface UseFormInputs { firstName: string lastName: string } export default function Form() { const { register, handleSubmit, reset, formState: { errors }, } = useForm() const onSubmit = (data: UseFormInputs) => { console.log(data) } return (
reset()} value="Custom Reset Field Values & Errors" />
) } ``` ```javascript import React, { useCallback } from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, reset } = useForm() const resetAsyncForm = useCallback(async () => { const result = await fetch("./api/formValues.json") // result: { firstName: 'test', lastName: 'test2' } reset(result) // 비동기로 폼 값을 초기화 }, [reset]) useEffect(() => { resetAsyncForm() }, [resetAsyncForm]) return (
{})}> { reset( { firstName: "bill", }, { keepErrors: true, keepDirty: true, } ) }} />
) } ``` **Controller** ```tsx import React from "react" import { useForm, Controller } from "react-hook-form" import { TextField } from "@material-ui/core" interface IFormInputs { firstName: string lastName: string } export default function App() { const { register, handleSubmit, reset, setValue, control } = useForm() const onSubmit = (data: IFormInputs) => console.log(data) return (
} name="firstName" control={control} rules={{ required: true }} defaultValue="" /> } name="lastName" control={control} defaultValue="" /> { reset({ firstName: "bill", lastName: "luo", }) }} /> ) } ``` ```javascript import { useForm, Controller } from "react-hook-form" import { TextField } from "@material-ui/core" export default function App() { const { register, handleSubmit, reset, setValue, control } = useForm() const onSubmit = (data) => console.log(data) return (
} name="firstName" control={control} rules={{ required: true }} defaultValue="" /> } name="lastName" control={control} defaultValue="" /> { reset({ firstName: "bill", lastName: "luo", }) }} /> ) } ``` **제출 후 초기화** ```javascript import { useForm, useFieldArray, Controller } from "react-hook-form" function App() { const { register, handleSubmit, reset, formState, formState: { isSubmitSuccessful }, } = useForm({ defaultValues: { something: "anything" } }) const onSubmit = (data) => { // 실행 순서가 중요하므로 useEffect에서 reset하는 것이 좋습니다. // reset({ ...data }) } React.useEffect(() => { if (formState.isSubmitSuccessful) { reset({ something: "" }) } }, [formState, submittedData, reset]) return (
) } ``` **필드 배열** ```javascript import React, { useEffect } from "react" import { useForm, useFieldArray, Controller } from "react-hook-form" function App() { const { register, control, handleSubmit, reset } = useForm({ defaultValues: { loadState: "unloaded", names: [{ firstName: "Bill", lastName: "Luo" }], }, }) const { fields, remove } = useFieldArray({ control, name: "names", }) useEffect(() => { reset({ names: [ { firstName: "Bob", lastName: "Actually", }, { firstName: "Jane", lastName: "Actually", }, ], }) }, [reset]) const onSubmit = (data) => console.log("data", data) return (
    {fields.map((item, index) => (
  • } name={`names.${index}.lastName`} control={control} />
  • ))}
) } ``` ### Videos --- --- # resetField > 필드 상태 및 값 재설정 ## \ `resetField:` `(name: string, options?: Record) => void` 개별 필드 상태를 재설정합니다. ### Props --- 이 함수를 호출한 후. - `isValid` 폼 상태가 재평가 됩니다. - `isDirty` 폼 상태가 재평가 됩니다. `ResetField`에는 필드 상태를 유지하는 기능이 있습니다. 사용할 수 있는 옵션은 다음과 같습니다: | Name | | Type | Description | | ------- | -------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | | string | 등록된 필드 이름. | | options | `keepError` | boolean | `true`로 설정하면, 필드 에러가 유지됩니다. | | | `keepDirty` | boolean | `true`로 설정하면, `dirtyFields`는 유지됩니다. | | | `keepTouched` | boolean | `true`로 설정하면, `touchedFields`상태는 변경되지 않습니다. | | | `defaultValue` | unknown | 값이 제공되지 **않은** 경우, 필드는 원래의 defaultValue로 돌아갑니다. 값이 제공되는 경우:
  • 필드 값이 주어진 값으로 업데이트됩니다.
  • 필드의 `defaultValue`도 해당 값으로 변경됩니다.
  • 단, undefined 값은 지원되지 않습니다.
| - 이름은 등록된 필드 이름과 일치해야 합니다. ```javascript register("test") resetField("test") // ✅ 입력 등록 및 재설정 필드 작동 resetField("non-existent-name") // ❌ 입력을 찾을 수 없어 실패 ``` **Examples:** --- **필드 상태 초기화** ```javascript import * as React from "react" import { useForm } from "react-hook-form" export default function App() { const { register, resetField, formState: { isDirty, isValid }, } = useForm({ mode: "onChange", defaultValues: { firstName: "", }, }) const handleClick = () => resetField("firstName") return (

{isDirty && "dirty"}

{isValid && "valid"}

) } ``` **옵션으로 재설정** ```javascript import * as React from "react" import { useForm } from "react-hook-form" export default function App() { const { register, resetField, formState: { isDirty, isValid, errors, touchedFields, dirtyFields }, } = useForm({ mode: "onChange", defaultValues: { firstName: "", }, }) return (

isDirty: {isDirty && "dirty"}

touchedFields: {touchedFields.firstName && "touched field"}

dirtyFields:{dirtyFields.firstName && "dirty field"}

isValid: {isValid && "valid"}

error: {errors.firstName && "error"}


) } ``` ### Video --- 다음 동영상 튜토리얼에서는 `resetField` API에 대해 설명합니다. --- # setError > 입력 에러를 수동으로 설정하기 ## \ `setError:` `(name: string, error: FieldError, { shouldFocus?: boolean }) => void` 이 함수는 하나 이상의 에러를 수동으로 설정할 수 있도록 합니다. ### Props --- | Name | Type | Description | | ------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | 입력 필드의 이름. | | `error` | `{ type: string, message?: string, types: MultipleFieldErrors }` | 에러 타입과 메시지를 설정합니다. | | config | `{ shouldFocus?: boolean }` | 에러 설정 시 입력 필드에 포커스를 줄지 여부를 설정합니다. 이 기능은 입력 필드의 참조가 등록되어 있을 때만 동작하며, custom register에는 작동하지 않습니다. | - 이 메서드는 입력이 `register`의 연관된 규칙을 통과한 경우, 관련 입력 에러를 유지하지 않습니다. ```javascript register("registerInput", { minLength: 4 }) setError("registerInput", { type: "custom", message: "custom message" }) // minLength 요구 사항을 충족하는 한 유효성 검사는 통과합니다. ``` - 입력 필드와 연관되지 않은 에러는 `clearErrors`를 통해 지워질 때까지 지속됩니다. 이 동작은 필드 수준에서의 내장 유효성 검사에만 해당됩니다. ```javascript setError("notRegisteredInput", { type: "custom", message: "custom message" }) // 해당 에러를 제거하려면 clearErrors()를 수동으로 호출해야 합니다. ``` - `root`를 키로 서버 또는 전역 에러를 설정할 수 있습니다. 이러한 유형의 에러는 각 submission마다 유지되지 않습니다. ```javascript setError("root.serverError", { type: "400", }) setError("root.random", { type: "random", }) ``` - 이 메서드는 비동기 유효성 검사 후, 사용자에게 에러 피드백을 제공하려는 경우, `handleSubmit` 메서드에서 유용할 수 있습니다. (예: API에서 유효성 검사 에러 반환 시) - `shouldFocus`는 입력 필드가 비활성화된 경우 작동하지 않습니다. - 이 메서드는 `isValid` 폼 상태를 `false`로 강제 설정합니다. 그러나 `isValid`는 항상 입력 등록 규칙 또는 스키마 결과의 유효성 검사 결과에서 파생된다는 점에 유의해야 합니다. - 타입 검사를 방해하지 않기 위해 `type`과 `types`라는 키워드는 피해야 합니다. **Examples:** --- **단일 에러 설정** ```tsx import * as React from "react" import { useForm } from "react-hook-form" type FormInputs = { username: string } const App = () => { const { register, handleSubmit, setError, formState: { errors }, } = useForm() const onSubmit = (data: FormInputs) => { console.log(data) } React.useEffect(() => { setError("username", { type: "manual", message: "Dont Forget Your Username Should Be Cool!", }) }, [setError]) return (
{errors.username &&

{errors.username.message}

}
) } ``` ```javascript import { useForm } from "react-hook-form" const App = () => { const { register, setError, formState: { errors }, } = useForm() return (
{errors.test &&

{errors.test.message}

}
) } ``` **다중 에러 설정** ```tsx import * as React from "react" import { useForm } from "react-hook-form" type FormInputs = { username: string firstName: string } const App = () => { const { register, handleSubmit, setError, formState: { errors }, } = useForm() const onSubmit = (data: FormInputs) => { console.log(data) } return (
{errors.username &&

{errors.username.message}

} {errors.firstName &&

{errors.firstName.message}

}
) } ``` ```javascript import * as React from "react" import { useForm } from "react-hook-form" const App = () => { const { register, handleSubmit, setError, formState: { errors }, } = useForm() const onSubmit = (data) => { console.log(data) } return (
{errors.username &&

{errors.username.message}

} {errors.firstName &&

{errors.firstName.message}

}
) } ``` **단일 필드 에러** ```tsx import * as React from "react" import { useForm } from "react-hook-form" type FormInputs = { lastName: string } const App = () => { const { register, handleSubmit, setError, formState: { errors }, } = useForm({ criteriaMode: "all", }) const onSubmit = (data: FormInputs) => console.log(data) React.useEffect(() => { setError("lastName", { types: { required: "이 필드는 필수입니다.", minLength: "최소 길이가 부족합니다.", }, }) }, [setError]) return (
{errors.lastName && errors.lastName.types && (

{errors.lastName.types.required}

)} {errors.lastName && errors.lastName.types && (

{errors.lastName.types.minLength}

)}
) } ``` ```javascript import * as React from "react" import { useForm } from "react-hook-form" const App = () => { const { register, handleSubmit, setError, formState: { errors }, } = useForm({ criteriaMode: "all", }) const onSubmit = (data) => { console.log(data) } React.useEffect(() => { setError("lastName", { types: { required: "이 필드는 필수입니다.", minLength: "최소 길이가 부족합니다.", }, }) }, [setError]) return (
{errors.lastName && errors.lastName.types && (

{errors.lastName.types.required}

)} {errors.lastName && errors.lastName.types && (

{errors.lastName.types.minLength}

)}
) } ``` **서버 에러 설정** ```javascript import * as React from "react"; import { useForm } from "react-hook-form"; const App = () => { const { register, handleSubmit, setError, formState: { errors } } = useForm({ criteriaMode: 'all', }); const onSubmit = async () => { const response = await fetch(...) if (response.statusCode > 200) { setError('root.serverError', { type: response.statusCode, }) } } return (
{errors.root.serverError.type === 400 &&

server response message

}
); }; ``` ### Video --- 다음 비디오는 `setError` API에 대해 자세히 설명합니다. --- # clearErrors > 폼 에러 제거 ## \ `clearErrors:` (name?: string | string[]) => void 이 함수는 폼의 에러를 직접 제거할 수 있습니다. ### Props --- | Type | Description | Example | | ------------------------------ | ----------------- | --------------------------------------- | | undefined | 모든 에러 제거. | `clearErrors()` | | string | 하나의 에러 제거. | `clearErrors("yourDetails.firstName")` | | string[] | 여러 에러 제거. | `clearErrors(["yourDetails.lastName"])` | - `undefined`: 모든 에러를 초기화합니다. - `string`: 하나의 필드 또는 키 이름으로 에러를 초기화합니다. ```javascript register("test.firstName", { required: true }) register("test.lastName", { required: true }) clearErrors("test") // test.firstName 및 test.lastName의 모든 에러를 제거 clearErrors("test.firstName") // 하나의 입력 에러 제거 ``` - `string[]`: 주어진 필드의 에러를 초기화합니다. - 이 메서드는 각 입력 필드에 연결된 유효성 검사 규칙에는 영향을 주지 않습니다. - 이 메서드는 유효성 검사 규칙이나 `isValid` formState에는 영향을 미치지 않습니다. **Examples** --- ```tsx sandbox="https://codesandbox.io/s/react-hook-form-v7-ts-clearerrors-w3ymx" import * as React from "react" import { useForm } from "react-hook-form" type FormInputs = { firstName: string lastName: string username: string } const App = () => { const { register, formState: { errors }, handleSubmit, clearErrors, } = useForm() const onSubmit = (data: FormInputs) => { console.log(data) } return (
) } ``` ```javascript sandbox="https://codesandbox.io/s/react-hook-form-v7-clearerrors-w5tl6" import * as React from "react" import { useForm } from "react-hook-form" const App = () => { const { register, formState: { errors }, handleSubmit, clearErrors, } = useForm() const onSubmit = (data) => console.log(data) return (
) } ``` --- # setValue > 필드 값 업데이트 ## \ `setValue:` (name: string, value: unknown, config?: SetValueConfig) => void 이 함수는 등록된(registered) 필드의 값을 동적으로 설정하고, 유효성 검사 및 폼 상태 업데이트와 관련된 옵션을 제공합니다. 동시에 불필요한 리렌더링을 방지합니다. ### Props --- | Name | | Description | | ---------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` string | | 이름으로 단일 필드를 타겟. | | `value` unknown | | 필드 값. 이 인자는 필수이며 `undefined`일 수 없습니다. | | `options` | `shouldValidate` boolean |
  • 입력값의 유효성을 계산할지 여부. (구독 대상: errors).
  • 전체 폼의 유효성을 계산할지 여부 (구독 대상: isValid).
  • 이 옵션은 전체 폼의 터치된 필드가 아닌 특정 필드 레벨에서만 `touchedFields`를 업데이트.
| | | `shouldDirty` boolean |
  • `defaultValues`와 비교해서 해당 입력이 수정되었는지 판단할지 여부를 결정. (구독 대상: dirtyFields).
  • `defaultValues`과 비교해서 전체 폼이 수정되었는지 판단할지 여부를 결정. (구독 대상: isDirty).
  • 이 옵션은 전체 폼 필드의 레벨이 아닌 특정 필드 레벨에서만 `dirtyFields`를 업데이트합니다.
| | | `shouldTouch` boolean | 입력 필드 자체를 터치된 상태로 설정할지 여부. | - 필드 배열에 [replace](/docs/usefieldarray#replace)나 [update](/docs/usefieldarray#update)와 같은 메서드를 사용할 수 있지만, 이 메서드들은 대상 필드 배열의 컴포넌트를 언마운트하고 다시 마운트하게 합니다. ```javascript const { update } = useFieldArray({ name: "array" }) // 필드를 언마운트하고 업데이트된 값으로 다시 마운트 update(0, { test: "1", test1: "2" }) // 입력 값을 직접 업데이트 setValue("array.0.test1", "1") setValue("array.0.test2", "2") ``` - 존재하지 않는 필드를 대상으로 지정하면 이 메서드는 새 필드를 생성하지 않습니다. ```javascript const { replace } = useFieldArray({ name: "test" }) // ❌ 새로운 입력 필드를 생성하지 않음 setValue("test.101.data") // ✅ 전체 필드 배열을 새로 고침 replace([{ data: "test" }]) ``` - 다음 조건에서만 리렌더링이 트리거됩니다: - 값 업데이트로 인해 에러가 발생하거나 수정된 경우. - `setValue`가 dirty나 touched와 같은 상태 업데이트를 유발하는 경우. - 두 번째 인자를 중첩된 객체로 만드는 것보다 필드 이름을 대상으로 설정하는 것이 좋습니다. ```javascript setValue("yourDetails.firstName", "value") // ✅ 성능이 뛰어남 setValue("yourDetails", { firstName: "value" }) ❌ // 성능이 떨어짐 register("nestedValue", { value: { test: "data" } }) // 중첩된 입력값을 등록(register) setValue("nestedValue.test", "updatedData") // ❌ 관련 필드를 찾지 못함 setValue("nestedValue", { test: "updatedData" }) // ✅ setValue가 입력을 찾고 업데이트 ``` - `setValue`를 호출하기 전에 입력의 이름을 등록하는 것이 좋습니다. `전체 필드 배열`을 업데이트하려면, `useFieldArray` 훅이 먼저 실행되고 있는지 확인하세요. **중요:** 전체 필드 배열을 업데이트할 때는 `setValue` 대신 `useFieldArray`의 `replace`를 사용하세요. `setValue`를 사용한 전체 필드 배열 업데이트는 다음 메이저 버전에서 제거될 예정입니다. ```javascript // 전체 필드 배열을 업데이트할 수 있습니다. setValue("fieldArray", [{ test: "1" }, { test: "2" }]) // ✅ // 등록되지 않은(unregistered) 입력 필드에도 setValue를 설정할 수 있습니다. setValue("notRegisteredInput", "value") // ✅ prefer to be registered // 다음 코드는 (register를 호출하지 않은 상태에서) 단일 입력 필드를 등록(register)합니다. setValue("resultSingleNestedField", { test: "1", test2: "2" }) // 🤔 // 등록된(registered) 입력 필드가 있을 경우, setValue가 두 입력을 모두 올바르게 업데이트합니다. register("notRegisteredInput.test", "1") register("notRegisteredInput.test2", "2") setValue("notRegisteredInput", { test: "1", test2: "2" }) // ✅ 두 번 setValue를 호출하는 것과 같은 문법적 설탕(sugar syntax) ``` ### Examples --- **Basic** ```javascript sandbox="https://codesandbox.io/s/react-hook-form-v7-ts-setvalue-8z9hx" import { useForm } from "react-hook-form" const App = () => { const { register, setValue } = useForm({ firstName: "", }) return (
) } ``` **Dependant Fields** ```tsx sandbox="https://codesandbox.io/s/dependant-field-dwin1" import * as React from "react" import { useForm } from "react-hook-form" type FormValues = { a: string b: string c: string } export default function App() { const { watch, register, handleSubmit, setValue, formState } = useForm({ defaultValues: { a: "", b: "", c: "", }, }) const onSubmit = (data: FormValues) => console.log(data) const [a, b] = watch(["a", "b"]) React.useEffect(() => { if (formState.touchedFields.a && formState.touchedFields.b && a && b) { setValue("c", `${a} ${b}`) } }, [setValue, a, b, formState]) return (
) } ``` ### Video --- --- # setFocus > 입력 포커스 수동 설정 ## `setFocus:` (name: string, options: SetFocusOptions) => void 이 메서드는 사용자가 프로그래밍적으로 입력 필드에 포커스를 설정할 수 있도록 합니다. 입력 필드의 ref가 훅 폼에 등록되었는지 확인하세요. ### Props --- | Name | | Type | Description | | --------- | -------------- | ---------------------------- | ------------------------------------ | | `name` | | string | 포커스를 설정할 입력 필드의 이름. | | `options` | `shouldSelect` | boolean | 포커스 시 입력 내용을 선택할지 여부. | - 이 API는 ref에서 focus 메서드를 호출하므로, `register` 시 반드시 `ref`를 제공해야 합니다. - `reset` API 사용 직후 `setFocus`를 호출하지 마세요.`reset`으로 인해 모든 입력 필드의 ref가 제거되기 때문입니다. ### Examples --- ```tsx import * as React from "react" import { useForm } from "react-hook-form" type FormValues = { firstName: string } export default function App() { const { register, handleSubmit, setFocus } = useForm() const onSubmit = (data: FormValues) => console.log(data) renderCount++ React.useEffect(() => { setFocus("firstName") }, [setFocus]) return (
) } ``` ```javascript import * as React from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, setFocus } = useForm() const onSubmit = (data) => console.log(data) renderCount++ React.useEffect(() => { setFocus("firstName") }, [setFocus]) return (
) } ``` --- # getValues > 폼 값 가져오기 ## \ `getValues:` `(payload?: string | string[]) => Object` 폼 값을 읽기 위한 최적화된 헬퍼입니다. `watch`와 `getValues`의 차이점은 `getValues`는 리렌더링을 발생시키지 않으며, 입력의 변경 사항을 구독하지 **않는다**는 점입니다. ### Props --- | Name | Type | Description | | ------------ | --------------- | ----------------------------------------------- | | `fieldNames` | `undefined` | 폼의 모든 값을 반환합니다. | | | `string` | 폼 값에서 지정된 경로의 값을 가져옵니다. | | | `array` | 폼 값에서 지정된 경로의 값을 배열로 반환합니다. | | `config` | `dirtyFields` | dirty fields만 반환합니다. | | | `touchedFields` | touchedFields만 반환합니다. | **Examples:** --- 아래 예제는 getValues 메서드를 호출할 때 기대할 수 있는 동작을 보여줍니다. ```jsx ``` | Name | Output | | ----------------------------------------------- | ----------------------------------- | | `getValues()` | `{ root: { test1: '', test2: ''} }` | | `getValues("root")` | `{ test1: '', test2: ''}` | | `getValues("root.firstName")` | `''` | | `getValues(["yourDetails.lastName"])` | `['']` | | `getValues(undefined, { dirtyFields: true })` | `{ root: { test1: '', test2: ''} }` | | `getValues(undefined, { touchedFields: true })` | `{ root: { test1: '', test2: ''} }` | - **초기** 렌더링 이전에는 `useForm`에서 제공된 `defaultValues`를 반환합니다. **예제:** --- ```tsx import React from "react" import { useForm } from "react-hook-form" type FormInputs = { test: string test1: string } export default function App() { const { register, getValues } = useForm() return (
) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, getValues } = useForm() return (
) } ``` ```tsx import React from "react" import { useForm } from "react-hook-form" // Flat input values type Inputs = { key1: string key2: number key3: boolean key4: Date } export default function App() { const { register, getValues } = useForm() getValues() return
} // Nested input values type Inputs1 = { key1: string key2: number key3: { key1: number key2: boolean } key4: string[] } export default function Form() { const { register, getValues } = useForm() getValues() // function getValues(): Record getValues("key1") // function getValues<"key1", unknown>(payload: "key1"): string getValues("key2") // function getValues<"key2", unknown>(payload: "key2"): number getValues("key3.key1") // function getValues<"key3.key1", unknown>(payload: "key3.key1"): unknown getValues("key3.key1") // function getValues(payload: string): number getValues("key3.key2") // function getValues(payload: string): boolean getValues("key4") // function getValues<"key4", unknown>(payload: "key4"): string[] return } ``` --- # getFieldState > 필드의 상태 ## \ `getFieldState:` `(name: string, formState?: Object) => ({isDirty, isTouched, invalid, error})` 이 메서드는 react-hook-form [v7.25.0](https://github.com/react-hook-form/react-hook-form/releases/tag/v7.25.0)에서 개별 필드 상태를 반환하기 위해 도입되었습니다. 이는 타입 안전한 방식으로 중첩된 필드 상태를 가져오려는 경우에 유용합니다. ### Props --- | Name | Type | Description | | --------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | 등록된 필드 이름. | | formState | `object` | 선택적 prop으로, useForm, useFormContext, 또는 useFormState에서 formState를 읽거나 구독하지 않는 경우에만 필요합니다. 자세한 내용은 rules를 참조하세요. | ### Return --- | Name | Type | Description | | --------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------- | | `isDirty` | `boolean` | 필드가 수정되었는지 여부. **조건:** `dirtyFields`를 구독해야 합니다. | | isTouched | `boolean` | 필드가 포커스와 블러 이벤트를 받았는지 여부. **조건:** `touchedFields`를 구독해야 합니다. | | invalid | `boolean` | 필드가 유효하지 않은지 여부. **조건:** `errors`를 구독해야 합니다. | | error | `undefined \| FieldError` | 필드 에러 객체. **조건:** `errors`를 구독해야 합니다. | - name은 등록된 필드 이름과 일치해야 합니다. ```javascript getFieldState("test") getFieldState("test") // ✅ 입력을 등록하고 필드 상태를 반환합니다 getFieldState("non-existent-name") // ❌ 상태를 false로, 에러를 undefined로 반환합니다 ``` - `getFieldState`는 폼 상태 업데이트를 구독하여 동작하며, 다음과 같은 방법으로 formState를 구독할 수 있습니다: - `useForm`, `useFormContext` 또는 `useFormState`에서 구독할 수 있습니다. 이는 formState 구독을 설정하고 `getFieldState`의 두 번째 인수가 더 이상 필요하지 않습니다. ```javascript const { register, formState: { isDirty }, } = useForm() register("test") getFieldState("test") // ✅ ``` ```javascript const { isDirty } = useFormState() register("test") getFieldState("test") // ✅ ``` ```javascript const { register, formState: { isDirty }, } = useFormContext() register("test") getFieldState("test") // ✅ ``` - 폼 상태 구독이 설정되어 있지 않은 경우, 다음 예제와 같이 전체 `formState`를 두 번째 선택적 인수로 전달할 수 있습니다: ```javascript const { register } = useForm() register("test") const { isDirty } = getFieldState("test") // ❌ useForm에서 formState의 isDirty가 구독되지 않음 const { register, formState } = useForm() const { isDirty } = getFieldState("test", formState) // ✅ formState.isDirty가 구독됨 const { formState } = useFormContext() const { touchedFields } = getFieldState("test", formState) // ✅ formState.touchedFields가 구독됨 ``` **Examples** --- ```javascript import * as React from "react" import { useForm } from "react-hook-form" export default function App() { const { register, getFieldState, formState: { isDirty, isValid }, } = useForm({ mode: "onChange", defaultValues: { firstName: "", }, }) // 렌더링 전이나 렌더링 함수 내에서 호출할 수 있습니다. const fieldState = getFieldState("firstName") return ( {" "}

{getFieldState("firstName").isDirty && "dirty"}

{" "}

{getFieldState("firstName").isTouched && "touched"}

) } ``` --- # trigger > 폼 전체에서 유효성 검사 트리거 ## `trigger:` `(name?: string | string[]) => Promise` 폼 또는 인풋의 유효성 검사를 수동으로 트리거합니다. 이 메서드는 의존적인 유효성 검사가 있는 경우(입력 유효성 검사가 다른 입력 값에 의존하는 경우)에도 유용합니다. ### Props --- | Name | Type | Description | Example | | ----------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | name | undefined | 모든 필드에서 유효성 검사 트리거. | `trigger()` | | | string | **name**으로 특정 필드 값에 대한 유효성 검사 트리거. | `trigger("yourDetails.firstName")` | | | string[] | **name**으로 여러 필드에 대한 유효성 검사 트리거. | `trigger(["yourDetails.lastName"])` | | shouldFocus | boolean | 에러를 설정하는 동안 입력에 초점을 맞춰야 합니다. 입력의 참조가 등록된(registered) 경우에만 작동하며, custom register에는 작동하지 않습니다. | `trigger('name', { shouldFocus: true })` | 분리 렌더링 최적화는 페이로드가 `string`인 단일 필드 이름을 대상으로 하는 경우에만 적용되며, 트리거에 `array`과 `undefined`를 제공하면 전체 formState를 다시 렌더링합니다. **Examples:** --- ```tsx import { useForm } from "react-hook-form" type FormInputs = { firstName: string lastName: string } export default function App() { const { register, trigger, formState: { errors }, } = useForm() return (
) } ``` ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, trigger, formState: { errors }, } = useForm() return (
) } ``` ### Video --- 다음 동영상에서는 `trigger` API에 대해 자세히 설명합니다. --- # control > 폼을 제어 ## \ `control:` Object 이 객체는 컴포넌트를 리액트 훅 폼에 등록할 수 있는 메소드들을 포함합니다. **Important:** 직접 이 객체의 속성에 접근하지 마세요. 속성은 패키지 내부 용도로만 사용합니다. **Examples:** --- ```tsx import React from "react" import { useForm, Controller } from "react-hook-form" import { TextField } from "@material-ui/core" type FormInputs = { firstName: string } function App() { const { control, handleSubmit } = useForm() const onSubmit = (data: FormInputs) => console.log(data) return (
} name="firstName" control={control} defaultValue="" /> ) } ``` ```javascript import { useForm, Controller } from "react-hook-form" function App() { const { control } = useForm() return ( } name="firstName" control={control} defaultValue="" /> ) } ``` --- # Form > 폼 submission 관리 ## \ `Form:` Component **Note**: 이 component는 현재 **BETA**입니다. 이 component는 선택 사항이며 표준 native 폼과 유사하게 폼 submission을 처리합니다. 기본적으로, 폼 submission 데이터는 [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)로 POST 요청을 보냅니다. FormData를 submission하지 않으려면 `headers` props를 제공하고 대신 `application/json`을 사용할 수 있습니다. - 폼을 점진적으로 개선합니다. - 리액트 웹과 리액트 네이티브를 모두 지원합니다. - 폼 submission 처리를 관리합니다. ```javascript
{}} // 요청 전에 호출할 함수 onSuccess={() => {}} // 유효한 응답 onError={() => {}} // 에러 응답 validateStatus={(status) => status >= 200} // status code 유효성 검사 /> ``` ### Props --- 모든 props는 optional입니다. | Name | Type | Description | Example | | ---------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `control` | `Control` | `useForm`을 호출하여 제공된 [`control`](/docs/useform/control) 객체. `FormProvider`를 사용할 경우 선택 사항. | "/> | | `children` | `React.ReactNode` | | | `render` | `Function` | headless component에 적합한 Render prop. | } />" /> | | `onSubmit` | `Function` | 유효성 검사 성공 후 호출되는 함수. | mutation(data)} />"/> | | `onSuccess` | `Function` | 서버 요청 성공 후 호출되는 함수. | {}} />" /> | | `onError` | `Function` | 서버 요청 실패 후 호출되는 함수. `setError` 함수는 오류 상태를 업데이트하기 위해 호출됩니다. `root.server`가 에러 키로 사용됩니다. | {}} />" /> | | `headers` | `Record` | 요청 헤더 객체. | "/> | | `validateStatus` | `(status: number) => boolean` | status code 유효성 검사 함수. | status === 200} />" /> | - submission 데이터를 준비하거나 생략하려면, [`handleSubmit`](/docs/useform/handlesubmit) or `onSubmit`을 사용하세요. ```javascript const { handleSubmit, control } = useForm(); const onSubmit =(data) => callback(prepareData(data)) // 혹은 { console.log(data) }} /> ``` - Progressive Enhancement는 SSR 프레임워크에만 적용됩니다. ```javascript const { handleSubmit, control } = useForm({ progressive: true }); // 렌더 ``` **예제:** --- **React Web** ```javascript import { useForm, Form } from "react-hook-form" function App() { const { control, register, formState: { isSubmitSuccessful, errors }, } = useForm({ // progressive: true, progressive enhancement를 위한 optional prop }) return (
// action prop을 사용하여 formData로 POST 요청
{ alert("Success") }} onError={() => { alert("error") }} > {" "} {isSubmitSuccessful &&

Form submit successful.

} {errors?.root?.server &&

Form submit failed.

}
// 수동 폼 submission
{ await fetch("api", { method: "post", body: formData, }) }} > {" "}
) } ``` **React Native** ```javascript import { useForm, Form } from "react-hook-form" function App() { const { control, register, formState: { isSubmitSuccessful, errors }, } = useForm() return (
{ ; {isSubmitSuccessful && Form submit successful.} {errors?.root?.server && Form submit failed.} {lens.map(fields, (value, l, index) => (
))}
) } function ContactForm({ lens, }: { lens: Lens<{ name: string; email: string }> }) { return (
ctrl.register(name))} /> ctrl.register(name))} />
) } ``` **Map callback parameters:** | Parameter | Type | Description | | ------------ | ----------- | ------------------------------ | | `value` | `T` | 현재 필드 값(`id` 포함) | | `lens` | `Lens` | 현재 배열 항목에 포커스된 렌즈 | | `index` | `number` | 현재 배열 인덱스 | | `array` | `T[]` | 전체 배열 | | `originLens` | `Lens` | 원본 배열 렌즈 | ### interop {#interop} `interop` 메서드는 렌즈가 내부적으로 사용하는 `control`과 `name` 속성을 노출하여 React Hook Form과의 자연스러운 통합을 제공합니다. 이를 통해 렌즈를 React Hook Form의 control API에 손쉽게 연결할 수 있습니다. #### 첫 번째 형태: 객체 반환 첫 번째 형태는 인수 없이 `interop()`을 호출하는 것으로, React Hook Form을 위한 `control` 및 `name` 속성을 포함하는 객체를 반환합니다: ```tsx const { control, name } = lens.interop() return ``` #### 두 번째 형태: 콜백 함수 두 번째 형태는 `interop`에 콜백 함수를 전달하는 방식입니다. 이 콜백은 `control`과 `name` 속성을 인자로 받아, 콜백 내부에서 직접 이 값들을 활용할 수 있습니다: ```tsx return (
ctrl.register(name))} />
) ``` #### useController와의 통합 `interop` 메서드의 반환값은 React Hook Form의 `useController` 훅에 직접 전달할 수 있어, 매끄러운 통합이 가능합니다: ```tsx import { useController } from "react-hook-form" function ControlledInput({ lens }: { lens: Lens }) { const { field, fieldState } = useController(lens.interop()) return (
{fieldState.error &&

{fieldState.error.message}

}
) } ``` ### useFieldArray 렌즈와 함께 배열을 손쉽게 다루려면 `@hookform/lenses/rhf`에서 확장된 `useFieldArray`를 import하세요. ```tsx import { useFieldArray } from "@hookform/lenses/rhf" function DynamicForm({ lens, }: { lens: Lens<{ items: { name: string; value: number }[] }> }) { const itemsLens = lens.focus("items") const { fields, append, remove, move } = useFieldArray(itemsLens.interop()) return (
{itemsLens.map(fields, (field, itemLens, index) => (
ctrl.register(name))} /> ctrl.register(name, { valueAsNumber: true }) )} /> {index > 0 && ( )}
))}
) } ``` - `control` 파라미터는 필수이며, React Hook Form의 `useForm` 훅에서 반환된 객체여야 합니다. - 각 렌즈는 성능 최적화를 위해 캐시에 저장되어 재사용되며, 동일한 경로를 여러 번 포커스하더라도 항상 동일한 렌즈 인스턴스가 반환됩니다. - `reflect` 등 함수형 메서드에 함수를 전달할 때는 캐싱 효과를 유지하려면 해당 함수를 메모이제이션하세요. - 의존성 배열(Dependencies Array)은 선택 사항이지만, 외부 상태 변화에 따라 렌즈 캐시를 초기화할 때 유용합니다. - 모든 렌즈 연산은 TypeScript 타입 안전성과 타입 추론을 완벽히 보장합니다. ### 예제 #### 기본 사용법 ```tsx import { useForm } from "react-hook-form" import { Lens, useLens } from "@hookform/lenses" import { useFieldArray } from "@hookform/lenses/rhf" function FormComponent() { const { handleSubmit, control } = useForm<{ firstName: string lastName: string children: { name: string surname: string }[] }>({}) const lens = useLens({ control }) return (
({ name: firstName, surname: lastName, }))} /> ) } function ChildForm({ lens, }: { lens: Lens<{ name: string; surname: string }[]> }) { const { fields, append } = useFieldArray(lens.interop()) return ( <> {lens.map(fields, (value, l) => ( ))} ) } // PersonForm은 서로 다른 소스와 함께 두 번 사용됩니다. function PersonForm({ lens, }: { lens: Lens<{ name: string; surname: string }> }) { return (
) } function StringInput({ lens }: { lens: Lens }) { return ctrl.register(name))} /> } ``` ### 동기 React Hook Form에서 복잡하고 깊이 중첩된 폼을 다루는 것은 금방 어려워질 수 있습니다. 기존 방식은 개발을 더 어렵고 오류가 발생하기 쉬운 여러 문제로 이어집니다: #### 1. 타입 안전한 Name 프롭은 사실상 불가능합니다 재사용 가능한 폼 컴포넌트를 만들려면 제어할 필드를 지정하기 위해 `name` 프롭을 받아야 합니다. 그러나 TypeScript에서 이를 타입 안전하게 만드는 것은 매우 어렵습니다: ```tsx // ❌ 타입 안전성을 잃음 - name이 폼 스키마와 일치하는지 확인할 방법이 없음 interface InputProps { name: string // 임의의 문자열일 수 있으며, 잘못된 필드 경로일 수도 있음 control: Control } // ❌ 적절한 타입 지정을 시도하면 복잡하고 유지 관리가 어려운 제네릭으로 이어집니다. interface InputProps> { name: TName control: Control } // 중첩 객체에서는 유지보수가 어렵고 쉽게 무너집니다 ``` #### 2. `useFormContext()`는 강한 결합을 만듭니다 `useFormContext()`를 재사용 가능한 컴포넌트에서 사용하면 특정 폼 스키마에 강하게 결합되어 유연성이 떨어지고 공유하기 어려워집니다: ```tsx // ❌ 부모 폼 구조에 강하게 결합됨 function AddressForm() { const { control } = useFormContext() // UserForm 타입에 고정됨 return (
{/* 고정된 필드 경로 */}
) } // 다른 폼 스키마로 이 컴포넌트를 재사용할 수 없음 ``` #### 3. 문자열 기반 필드 경로는 오류를 유발하기 쉽습니다 문자열 연결로 필드 경로를 만드는 재사용 가능한 컴포넌트 방식은 매우 취약하고 유지보수가 어렵습니다: ```tsx // ❌ 문자열 연결 방식은 오류가 발생하기 쉽고 리팩터링이 어렵습니다 function PersonForm({ basePath }: { basePath: string }) { const { register } = useForm(); return (
{/* 타입 안전성이 없고, 오타에 취약합니다 */}
); } // 사용법이 번거롭고 오류가 발생하기 쉽습니다 ``` ### 성능 최적화 #### 내장 캐싱 시스템 렌즈는 `React.memo`를 사용할 때 불필요한 컴포넌트 리렌더링을 방지하기 위해 자동으로 캐싱됩니다. 즉, 동일한 경로에 여러 번 포커스해도 항상 동일한 렌즈 인스턴스가 반환됩니다: ```tsx assert(lens.focus("firstName") === lens.focus("firstName")) ``` #### 함수 메모이제이션 `reflect`와 같은 메서드에 함수를 전달할 때는 캐싱 효과를 유지하려면 함수의 동일성에 주의해야 합니다: ```tsx // ❌ 매 렌더마다 새로운 함수를 생성하여 캐시가 깨집니다 lens.reflect((l) => l.focus("firstName")) ``` 캐싱을 유지하려면, 전달하는 함수를 반드시 메모이제이션하세요: ```tsx // ✅ 메모이제이션된 함수는 캐시를 보존합니다 lens.reflect(useCallback((l) => l.focus("firstName"), [])) ``` [React Compiler](https://react.dev/learn/react-compiler)는 이러한 함수들을 자동으로 최적화해줍니다! `reflect`에 전달되는 함수는 부작용이 없으므로, React Compiler가 자동으로 해당 함수를 모듈 스코프로 끌어올려 렌즈 캐싱이 수동 메모이제이션 없이도 완벽하게 동작하도록 보장합니다. ### 고급 사용법 #### 수동 렌즈 생성 더 고급 사용 사례나 세밀한 제어가 필요할 때는 `useLens` 훅 없이 `LensCore` 클래스를 사용해 렌즈를 직접 생성할 수 있습니다: ```tsx import { useMemo } from "react" import { useForm } from "react-hook-form" import { LensCore, LensesStorage } from "@hookform/lenses" function App() { const { control } = useForm<{ firstName: string; lastName: string }>() const lens = useMemo(() => { const cache = new LensesStorage(control) return LensCore.create(control, cache) }, [control]) return (
ctrl.register(name))} /> ctrl.register(name))} />
) } ``` 버그를 발견하셨거나 기능 요청이 있으신가요? [GitHub 저장소](https://github.com/react-hook-form/lenses)에서 이슈를 등록하거나 프로젝트에 기여해 주세요. --- # createFormControl > 폼 상태를 생성하고 구독할 수 있도록 준비합니다. 이 함수는 전체 폼 상태 구독을 생성하고, 리액트 컴포넌트 유무에 상관없이 업데이트를 구독할 수 있게 합니다. React Context API 없이도 이 함수를 사용할 수 있습니다. ### Props --- | Name | Type | Description | | ---------- | --------------------------- | -------------- | | `...props` | Object | `UseFormProps` | ### Returns --- | Name | Type | Description | | ------------- | ------------------------------ | -------------------------------------------------------------------------------- | | `formControl` | Object | `useForm` 훅을 위한 control 객체 | | `control` | Object | `useController`, `useFormState`, `useWatch`를 위한 control 객체 | | `subscribe` | Function | 렌더링을 발생시키지 않고 폼 상태 변화를 [감지](/docs/useform/subscribe)하는 함수 | | `...returns` | Functions | `useForm`이 반환하는 메서드들 | - 이 함수는 **v7.55.0** 버전부터 제공됩니다. - 이 함수는 완전히 선택적 기능으로, `useFormContext` API 대신 사용하는 것을 고려해볼 수 있습니다. - 리액트 리렌더링을 건너뛰어 formsState를 구독하고자 할 때 유용하게 사용할 수 있습니다. - 이 API 또는 Context API 중 하나를 사용해야 합니다. ```tsx const props = createFormControl() // ❌ provider가 필요 없습니다.{" "} // ✅ createFormControl에서 직접 사용하는 메서드입니다 ``` **Examples:** --- ```javascript const { formControl, control, handleSubmit, register } = createFormControl({ mode: 'onChange', defaultValues: { firstName: 'Bill' } }}) function App() { useForm({ formControl, }) return (
console.log)}> ); } function FormState() { useFormState({ control // context api가 더이상 필요하지 않음. }) } function Controller() { useFormState({ control // context api가 더이상 필요하지 않음. }) } ``` ```javascript const { formControl, register } = createFormControl(props) formControl.subscribe({ formState: { isDirty: true, values: true, }, callback: (formState) => { if (formState.isDirty) { // do something here } if (formState.values.test.length > 3) { // do something here } }, }) function App() { const { register } = useForm({ formControl, }) return (
) } ``` --- # 고급 사용법 > 복잡하면서도 접근성 높은 폼 만들기 ## 접근성 (A11y) {#AccessibilityA11y} React Hook Form은 네이티브 폼 검증을 지원하여, 사용자가 정의한 규칙으로 입력값을 검증할 수 있습니다. 대부분의 경우 우리는 커스텀 디자인과 레이아웃의 폼을 만들어야 하므로, 이러한 폼이 접근성(A11y)을 충분히 갖추도록 하는 것은 우리의 책임입니다. 아래 코드 예제는 유효성 검사 자체는 의도한 대로 작동하지만, 접근성 측면에서는 개선의 여지가 있습니다. ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, formState: { errors }, } = useForm() const onSubmit = (data) => console.log(data) return (
{errors.name && errors.name.type === "required" && ( This is required )} {errors.name && errors.name.type === "maxLength" && ( Max length exceeded )}
) } ``` 다음 코드 예제는 [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)를 활용해 개선된 버전입니다. ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, formState: { errors }, } = useForm() const onSubmit = (data) => console.log(data) return (
{/* 필드에 에러가 있음을 알리기 위해 aria-invalid 속성을 사용 */} {/* 에러 메시지를 제공하기 위해 role="alert" 속성을 사용. */} {errors.name && errors.name.type === "required" && ( This is required )} {errors.name && errors.name.type === "maxLength" && ( Max length exceeded )}
) } ``` 이렇게 개선하면 스크린 리더가 다음과 같이 읽어줍니다: _“이름, 편집, 잘못된 입력, 이 필드는 필수입니다.”_ --- ## 단계별 폼(Wizard Form) / 퍼널 {#WizardFormFunnel} 여러 페이지나 섹션에 걸쳐 사용자 정보를 수집하는 것은 매우 일반적인 패턴입니다. 각 페이지나 섹션을 오가며 유저가 입력한 값을 유지하려면 상태 관리 라이브러리를 사용하는 것을 권장합니다. 이 예제에서는 [little state machine](https://github.com/bluebill1049/little-state-machine)을 상태 관리 라이브러리로 사용합니다(만약 redux에 더 친숙하다면, 이를 [redux](https://github.com/reduxjs/redux)로 대체할 수 있습니다). **1단계:** 라우트(routes)와 스토어(store)를 설정합니다. ```javascript import { BrowserRouter as Router, Route } from "react-router-dom" import { StateMachineProvider, createStore } from "little-state-machine" import Step1 from "./Step1" import Step2 from "./Step2" import Result from "./Result" createStore({ data: { firstName: "", lastName: "", }, }) export default function App() { return ( ) } ``` **2단계:** 페이지들을 만들고, 데이터를 수집하고 스토어(store)에 저장한 뒤, 다음 폼/페이지로 이동합니다. ```javascript import { useForm } from "react-hook-form" import { withRouter } from "react-router-dom" import { useStateMachine } from "little-state-machine" import updateAction from "./updateAction" const Step1 = (props) => { const { register, handleSubmit } = useForm() const { actions } = useStateMachine({ updateAction }) const onSubmit = (data) => { actions.updateAction(data) props.history.push("./step2") } return (
) } export default withRouter(Step1) ``` **3단계:** 스토어(store)에 쌓인 모든 데이터를 최종 제출하거나, 결과를 화면에 표시합니다. ```javascript import { useStateMachine } from "little-state-machine" import updateAction from "./updateAction" const Result = (props) => { const { state } = useStateMachine(updateAction) return
{JSON.stringify(state, null, 2)}
} ``` 위와 같은 흐름을 따르면, 여러 페이지에 걸쳐 사용자 입력 데이터를 모아 처리하는 단계별 폼(Wizard Form)/퍼널을 구현할 수 있습니다. --- ## 똑똑한 폼 컴포넌트 {#SmartFormComponent} 여기서의 아이디어는 입력 컴포넌트를 이용하여 손쉽게 폼을 구성하는 것입니다. 폼 데이터를 자동으로 수집하는 `Form` 컴포넌트를 만들어 보겠습니다. ```javascript import { Form, Input, Select } from "./Components" export default function App() { const onSubmit = (data) => console.log(data) return (
) } ``` 각 컴포넌트가 어떤 역할을 하는지 살펴보겠습니다. ` Form` `Form` 컴포넌트는 react-hook-form의 모든 메서드를 자식 컴포넌트에 주입하는 역할을 합니다. ```javascript import { Children, createElement } from "react" import { useForm } from "react-hook-form" export default function Form({ defaultValues, children, onSubmit }) { const methods = useForm({ defaultValues }) const { handleSubmit } = methods return (
{Children.map(children, (child) => { return child.props.name ? createElement(child.type, { ...{ ...child.props, register: methods.register, key: child.props.name, }, }) : child })}
) } ``` ` Input / Select` `Input`/`Select` 컴포넌트는 react-hook-form에 자신을 등록(register)하는 역할을 담당합니다. ```javascript export function Input({ register, name, ...rest }) { return } export function Select({ register, options, name, ...rest }) { return ( ) } ``` `Form` 컴포넌트가 `react-hook-form`의 `속성`(props)을 자식 컴포넌트에게 주입해주기 때문에 앱에서 복잡한 폼도 손쉽게 생성하고 조합할 수 있습니다. --- ## 에러 메세지 {#ErrorMessages} 에러 메세지는 사용자 입력에 문제가 있을 때 사용자에게 시각적 피드백을 제공합니다. React Hook Form은 `errors` 객체를 제공하여 에러 정보를 손쉽게 가져올 수 있도록 합니다. 화면에 에러를 더 깔끔하게 표시하는 방법에는 여러 가지가 있습니다. - #### Register 유효성 검증 규칙 객체의 `message` 속성을 통해, 아래와 같이 `register`에 바로 에러 메시지를 전달할 수 있습니다.: `` - #### 옵셔널 체이닝(Optional Chaining) `?.` [옵셔널 체이닝(optional chaining)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) 연산자를 사용하면 `errors` 객체를 조회할 때 `null` 또는 `undefined`로 인한 추가 에러 발생을 걱정하지 않아도 됩니다. `errors?.firstName?.message` - #### Lodash `get` 프로젝트에서 [lodash](https://lodash.com)를 사용 중이라면, lodash [get](https://lodash.com/docs/4.17.15#get) 함수를 사용하여 깊이 있는 경로의 에러 메시지를 가져올 수 있습니다. 예: `get(errors, 'firstName.message')` --- ## 폼 연결하기 {#ConnectForm} 폼을 구성할 때 입력 컴포넌트가 깊게 중첩된 트리 구조 안에 위치하는 경우가 있습니다. 이 때 [FormContext](/docs/useformcontext)를 사용하면 편리합니다. 하지만, `ConnectForm` 컴포넌트를 만들고 리액트의 [renderProps](https://reactjs.org/docs/render-props.html)를 활용하면 개발자 경험을 더욱 향상시킬 수 있습니다. 이 방식을 사용하면 입력 컴포넌트를 React Hook Form에 훨씬 더 쉽게 연결할 수 있다는 장점이 있습니다. ```javascript import { FormProvider, useForm, useFormContext } from "react-hook-form" export const ConnectForm = ({ children }) => { const methods = useFormContext() return children(methods) } export const DeepNest = () => ( {({ register }) => } ) export const App = () => { const methods = useForm() return (
) } ``` --- ## FormProvider 성능 {#FormProviderPerformance} React Hook Form의 [FormProvider](/docs/formprovider)는 [React의 Context](https://react.dev/learn/passing-data-deeply-with-context) API 위에 구축되었습니다. 이는 데이터가 컴포넌트 트리를 따라 전달될 때 매 단계마다 수동으로 props를 내려줘야 한다는 문제를 해결합니다. 다만, React Hook Form이 상태 업데이트를 트리거할 때 컴포넌트 트리가 다시 렌더링되는 부작용이 생깁니다. 필요하다면 아래 예제를 통해 앱을 최적화할 수 있습니다. **참고:** React Hook Form의 [Devtools](/dev-tools)을 [FormProvider](/docs/formprovider)와 함께 사용하면 일부 상황에서 성능 문제가 발생할 수 있습니다. 본격적인 최적화에 들어가기 전, 이 병목 현상을 먼저 고려하세요. ```javascript import { memo } from "react" import { useForm, FormProvider, useFormContext } from "react-hook-form" // isDirty 상태가 변경될 때를 제외하고는 리렌더되지 않도록 React.memo를 사용할 수 있습니다. const NestedInput = memo( ({ register, formState: { isDirty } }) => (
{isDirty &&

This field is dirty

}
), (prevProps, nextProps) => prevProps.formState.isDirty === nextProps.formState.isDirty ) export const NestedInputContainer = ({ children }) => { const methods = useFormContext() return } export default function App() { const methods = useForm() const onSubmit = (data) => console.log(data) console.log(methods.formState.isDirty) // Proxy를 활성화하려면 렌더링 전에 formState를 반드시 읽어야 합니다. return (
) } ``` --- ## 제어 컴포넌트와 비제어 컴포넌트의 혼합 사용 {#ControlledmixedwithUncontrolledComponents} React Hook Form은 비제어 컴포넌트를 기본으로 하지만, 제어 컴포넌트와도 완벽히 호환합니다. [MUI](https://github.com/mui/material-ui)와 [Antd](https://github.com/ant-design/ant-design) 같은 대부분의 UI 라이브러리는 제어 컴포넌트만 지원하도록 설계되어 있지만, React Hook Form을 사용하면 제어 컴포넌트의 리렌더링도 최적화할 수 있습니다. 아래 예제는 제어 컴포넌트와 비제어 컴포넌트를 유효성 검증과 함께 결합한 코드입니다. ```javascript import { Input, Select, MenuItem } from "@material-ui/core" import { useForm, Controller } from "react-hook-form" const defaultValues = { select: "", input: "", } function App() { const { handleSubmit, reset, control, register } = useForm({ defaultValues, }) const onSubmit = (data) => console.log(data) return (
( )} control={control} name="select" defaultValue={10} /> ) } ``` ```javascript import { useEffect } from "react" import { Input, Select, MenuItem } from "@material-ui/core" import { useForm } from "react-hook-form" const defaultValues = { select: "", input: "", } function App() { const { register, handleSubmit, setValue, reset, watch } = useForm({ defaultValues, }) const selectValue = watch("select") const onSubmit = (data) => console.log(data) useEffect(() => { register("select") }, [register]) const handleChange = (e) => setValue("select", e.target.value) return (
) } ``` --- ## Resolver를 활용한 커스텀 훅 {#CustomHookwithResolver} 커스텀 훅을 resolver로 구현할 수 있습니다. 이렇게 하면 커스텀 훅은 yup/Joi/Superstruct와 같은 유효성 검증 메서드와 쉽게 통합할 수 있으며, 유효성 검증 resolver 내부에서 활용할 수 있습니다. - 메모이제이션된 유효성 검증 스키마를 정의합니다.(의존성이 없다면 컴포넌트 외부에 정의해도 됩니다.) - 유효성 검증 스키마를 전달하여 커스텀 훅을 사용합니다. - 생성된 유효성 검증 resolver를 `useForm` 훅에 전달합니다. ```javascript import { useCallback } from "react" import { useForm } from "react-hook-form" import * as yup from "yup" const useYupValidationResolver = (validationSchema) => useCallback( async (data) => { try { const values = await validationSchema.validate(data, { abortEarly: false, }) return { values, errors: {}, } } catch (errors) { return { values: {}, errors: errors.inner.reduce( (allErrors, currentError) => ({ ...allErrors, [currentError.path]: { type: currentError.type ?? "validation", message: currentError.message, }, }), {} ), } } }, [validationSchema] ) const validationSchema = yup.object({ firstName: yup.string().required("Required"), lastName: yup.string().required("Required"), }) export default function App() { const resolver = useYupValidationResolver(validationSchema) const { handleSubmit, register } = useForm({ resolver }) return (
console.log(data))}>
) } ``` --- ## 가상화된(virtualized) 리스트 다루기 {#Workingwithvirtualizedlists} 데이터 테이블이 있는 시나리오를 상상해보세요. 이 테이블에는 수백 또는 수천 개의 행이 있을 수 있으며, 각 행에는 입력 필드가 들어 있습니다. 일반적으로 뷰포트에 보이는 항목만 렌더링하는 방식을 사용하지만, 이 경우 항목이 뷰포트 밖으로 나가면 DOM에서 제거되었다가 다시 추가되면서, 다시 보이는 순간 입력값이 기본값으로 리셋되는 문제가 발생할 수 있습니다. 아래 예제는 [react-window](https://github.com/bvaughn/react-window)를 사용해 이를 보여줍니다. ```javascript import { memo } from "react" import { FormProvider, useForm, useFormContext } from "react-hook-form" import { VariableSizeList as List } from "react-window" import AutoSizer from "react-virtualized-auto-sizer" const items = Array.from(Array(1000).keys()).map((i) => ({ title: `List ${i}`, quantity: Math.floor(Math.random() * 10), })) const WindowedRow = memo(({ index, style, data }) => { const { register } = useFormContext() return (
) }) export const App = () => { const onSubmit = (data) => console.log(data) const methods = useForm({ defaultValues: items }) return (
{({ height, width }) => ( 100} width={width} itemData={items} > {WindowedRow} )}
) } ``` ```javascript import { FixedSizeList } from "react-window" import { Controller, useFieldArray, useForm } from "react-hook-form" const items = Array.from(Array(1000).keys()).map((i) => ({ title: `List ${i}`, quantity: Math.floor(Math.random() * 10), })) function App() { const { control, getValues } = useForm({ defaultValues: { test: items, }, }) const { fields } = useFieldArray({ control, name: "test" }) return ( fields[i].id} > {({ style, index, data }) => { const defaultValue = getValues()["test"][index].quantity ?? data[index].quantity return (
} name={`test[${index}].quantity`} defaultValue={defaultValue} control={control} /> ) }}
) } ``` --- ## 폼 테스트 {#TestingForm} 테스트는 코드에 버그나 실수가 없도록 방지해 주기 때문에 매우 중요합니다. 또한 테스트는 코드베이스를 리팩토링할 때 안전성을 보장합니다. 간단하고 사용자 행동 중심의 테스트를 작성할 수 있는 [testing-library](https://testing-library.com/) 사용을 권장합니다. **1단계:** 테스트 환경 설정 React Hook Form은 입력 필드를 감지하고 DOM에서 언마운트되는 것을 확인하기 위해 `MutationObserver`를 사용하므로, 최신 버전의 `jest`와 함께 [@testing-library/jest-dom](https://github.com/testing-library/jest-dom)을 설치하세요. **참고:** React Native를 사용 중이라면, [@testing-library/jest-dom](https://github.com/testing-library/jest-dom)을 설치할 필요가 없습니다. ```bash npm install -D @testing-library/jest-dom ``` [@testing-library/jest-dom](https://github.com/testing-library/jest-dom)를 import할 `setup.js`를 생성하세요. ```javascript import "@testing-library/jest-dom" ``` **참고:** React Native를 사용 중이라면, setup.js을 생성하고, `window` 객체를 정의한 뒤, 아래 내용을 setup 파일에 추가해야 합니다: ```javascript global.window = {} global.window = global ``` 마지막으로,`jest.config.js`에서 `setup.js`를 포함하도록 업데이트해야 합니다. ```javascript module.exports = { setupFilesAfterEnv: ["/setup.js"], // 또는 TypeScript App을 위한 .ts // ...다른 settings } ``` 추가로, 테스트 작성 시 모범 사례를 준수하고 자주 발생하는 실수를 방지하기 위해 [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library)와 [eslint-plugin-jest-dom](https://github.com/testing-library/eslint-plugin-jest-dom) 플러그인을 설정할 수 있습니다. **Step 2:** 로그인 폼 생성하기. role 속성을 적절히 설정했습니다. 이 속성들은 테스트 작성 시 유용하며, 접근성을 향상시키는 데도 도움이 됩니다. 자세한 내용은 [testing-library](https://testing-library.com/) 문서를 참고하세요. ```javascript import { useForm } from "react-hook-form" export default function App({ login }) { const { register, handleSubmit, formState: { errors }, reset, } = useForm() const onSubmit = async (data) => { await login(data.email, data.password) reset() } return (
{errors.email && {errors.email.message}} {errors.password && {errors.password.message}}
) } ``` **3단계:** 테스트 작성하기. 다음 기준을 테스트로 커버합니다: - 제출 실패 테스트 `handleSubmit` 메서드는 비동기적으로 실행되기 때문에, `waitFor` 유틸과 `find*` 쿼리를 사용해 제출 피드백을 확인합니다. - 유효성 검증과 관련있는 각 입력 필드 테스트 사용자가 UI 컴포넌트를 인식하는 방식에 맞춰 `*ByRole` 메서드를 사용해 각 요소를 조회합니다. - 제출 성공 테스트 ```javascript import { render, screen, fireEvent, waitFor } from "@testing-library/react" import App from "./App" const mockLogin = jest.fn((email, password) => { return Promise.resolve({ email, password }) }) it("should display required error when value is invalid", async () => { render() fireEvent.submit(screen.getByRole("button")) expect(await screen.findAllByRole("alert")).toHaveLength(2) expect(mockLogin).not.toBeCalled() }) it("should display matching error when email is invalid", async () => { render() fireEvent.input(screen.getByRole("textbox", { name: /email/i }), { target: { value: "test", }, }) fireEvent.input(screen.getByLabelText("password"), { target: { value: "password", }, }) fireEvent.submit(screen.getByRole("button")) expect(await screen.findAllByRole("alert")).toHaveLength(1) expect(mockLogin).not.toBeCalled() expect(screen.getByRole("textbox", { name: /email/i })).toHaveValue("test") expect(screen.getByLabelText("password")).toHaveValue("password") }) it("should display min length error when password is invalid", async () => { render() fireEvent.input(screen.getByRole("textbox", { name: /email/i }), { target: { value: "test@mail.com", }, }) fireEvent.input(screen.getByLabelText("password"), { target: { value: "pass", }, }) fireEvent.submit(screen.getByRole("button")) expect(await screen.findAllByRole("alert")).toHaveLength(1) expect(mockLogin).not.toBeCalled() expect(screen.getByRole("textbox", { name: /email/i })).toHaveValue( "test@mail.com" ) expect(screen.getByLabelText("password")).toHaveValue("pass") }) it("should not display error when value is valid", async () => { render() fireEvent.input(screen.getByRole("textbox", { name: /email/i }), { target: { value: "test@mail.com", }, }) fireEvent.input(screen.getByLabelText("password"), { target: { value: "password", }, }) fireEvent.submit(screen.getByRole("button")) await waitFor(() => expect(screen.queryAllByRole("alert")).toHaveLength(0)) expect(mockLogin).toBeCalledWith("test@mail.com", "password") expect(screen.getByRole("textbox", { name: /email/i })).toHaveValue("") expect(screen.getByLabelText("password")).toHaveValue("") }) ``` #### 테스트 중 act 경고 해결하기 react-hook-form을 사용하는 컴포넌트를 테스트할 때, 해당 컴포넌트에 비동기 코드를 작성하지 않아도 다음과 같은 경고가 발생할 수 있습니다: > Warning: An update to MyComponent inside a test was not wrapped in act(...) ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm({ mode: "onChange", }) const onSubmit = (data) => {} return (
) } ``` ```javascript import { render, screen } from "@testing-library/react" import App from "./App" it("should have a submit button", () => { render() expect(screen.getByText("SUBMIT")).toBeInTheDocument() }) ``` 이 예제에서는 명백히 비동기 코드가 없는 간단한 폼을 렌더링하고 버튼이 있는지만 테스트합니다. 그러나 여전히 업데이트가 `act()`로 래핑되지 않았다는 경고가 출력됩니다. 이는 react-hook-form이 내부적으로 비동기 유효성 검증 핸들러를 사용하기 때문입니다. `formState`를 계산하기 위해 초기 폼 유효성 검증을 비동기적으로 수행해야 하고, 이로 인해 추가적인 렌더링이 발생합니다. 테스트 함수가 반환된 후에 이 업데이트가 일어나면서 경고가 발생하는 것입니다. 해결 방법은 `find*` 쿼리를 사용해 UI의 특정 요소가 나타날 때까지 기다리는 것입니다. 이 때 `render()`를 `act()`로 감싸면 **안된다는 점**을 기억하세요. [불필요하게 `act`로 래핑하는 것과 관련된 내용을 더 알아보려면 여기를 참고하세요.](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#wrapping-things-in-act-unnecessarily). ```javascript import { render, screen } from "@testing-library/react" import App from "./App" it("should have a submit button", async () => { render() expect(await screen.findByText("SUBMIT")).toBeInTheDocument() // UI가 비동기 동작이 완료될 때까지 대기했으므로, // 이제 `get*` 쿼리를 사용해 계속해서 검증할 수 있습니다. expect(screen.getByRole("textbox")).toBeInTheDocument() }) ``` --- ## 변환 및 파싱 {#TransformandParse} 네이티브 `input` 요소는 `valueAsNumber`나 `valueAsDate`를 사용하지 않으면 항상 값을 `string` 형식으로 반환합니다. 자세한 내용은 [이 부분](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement)을 참고하세요. 하지만 이 방식만으로는 완벽하지 않습니다. 여전히 `isNaN`이나 `null` 값을 직접 처리해야 하기 때문입니다. 따라서 변환 로직은 커스텀 훅 수준에서 처리하는 것이 좋습니다. 아래 예제에서는, `Controller`를 사용하여 입력값의 변환과 출력 기능을 함께 구현했습니다. 커스텀 `register`를 사용해도 유사한 결과를 얻을 수 있습니다. ```javascript import { Controller } from "react-hook-form" const ControllerPlus = ({ control, transform, name, defaultValue }) => ( ( field.onChange(transform.output(e))} value={transform.input(field.value)} /> )} /> ) // 사용 예시는 아래와 같음: (isNaN(value) || value === 0 ? "" : value.toString()), output: (e) => { const output = parseInt(e.target.value, 10) return isNaN(output) ? 0 : output }, }} control={control} name="number" defaultValue="" /> ``` --- # FAQs > 자주 묻는 질문 ## React Hook Form의 성능 {#PerformanceofReactHookForm} 성능은 이 라이브러리가 만들어진 주요 이유 중 하나입니다. React Hook Form은 비제어 컴포넌트 방식을 사용하며, 이것이 `register` 함수가 `ref`를 캡처하고 제어 컴포넌트가 `Controller` 또는 `useController`로 자체 리렌더링 범위를 갖는 이유입니다. 이 접근 방식은 사용자가 입력 필드에 타이핑하거나 다른 폼 값이 변경될 때 폼이나 애플리케이션의 루트에서 발생하는 리렌더링의 양을 줄입니다. 비제어 컴포넌트는 오버헤드가 적기 때문에 제어 컴포넌트보다 페이지에 더 빠르게 마운트됩니다. 참고로 [이 저장소 링크](https://github.com/bluebill1049/react-hook-form-performance-compare)에서 간단한 성능 비교 테스트를 확인할 수 있습니다. --- ## 어떻게 접근 가능한 입력 에러 및 메시지를 만드나요? {#Howtocreateanaccessibleinputerrorandmessage} React Hook Form은 비제어 컴포넌트를 기반으로 하므로, 접근 가능한 커스텀 폼을 쉽게 만들 수 있습니다. _(비제어 컴포넌트에 대한 자세한 내용은 [컴포넌트 간 State 공유하기](https://react.dev/learn/sharing-state-between-components)를 참고하세요)_ ```javascript import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, formState: { errors }, } = useForm() const onSubmit = (data) => console.log(data) return (
{errors.firstName && This field is required}
) } ``` --- ## 클래스 컴포넌트에서 동작하나요? {#DoesitworkwithClassComponents} 아니요, 기본적으로는 작동하지 않습니다. 사용하고 싶다면 wrapper를 만들어서 클래스 컴포넌트에서 사용할 수 있습니다. > 클래스 컴포넌트 내부에서는 Hook을 사용할 수 없지만, 하나의 트리 안에서 클래스 컴포넌트와 Hook을 사용하는 함수 컴포넌트를 혼합하여 사용할 수는 있습니다. 컴포넌트가 클래스인지 Hook을 사용하는 함수인지는 단순히 해당 컴포넌트의 구현 세부 사항일 뿐입니다. 장기적으로 우리는 Hook이 React 컴포넌트를 작성하는 주요 방법이 될 것으로 기대합니다. --- ## 폼을 어떻게 리셋하나요? {#Howtoresettheform} 폼을 리셋하는 두 가지 방법이 있습니다: - **HTMLFormElement.reset()** 이 메서드는 폼의 리셋 버튼을 클릭하는 것과 동일한 동작을 수행합니다. `input/select/checkbox` 값만 초기화합니다. - **React Hook Form API: `reset()`** React Hook Form의 `reset` 메서드는 모든 필드 값을 리셋하고, 폼 내의 모든 `errors`도 제거합니다. --- ## 어떻게 폼 값을 초기화하나요? {#Howtoinitializeformvalues} React Hook Form은 비제어 컴포넌트 방식을 사용하므로, 개별 필드에 `defaultValue` 또는 `defaultChecked`를 지정할 수 있습니다. 하지만 `useForm`에 `defaultValues`를 전달하여 폼을 초기화하는 것이 더 일반적이고 권장되는 방법입니다. ```javascript function App() { const { register, handleSubmit } = useForm({ defaultValues: { firstName: "bill", lastName: "luo", }, }) return (
console.log(data))}>
) } ``` 비동기 기본값의 경우 다음 방법을 사용할 수 있습니다: - 비동기 `defaultValues` ```javascript copy function App() { const { register, handleSubmit } = useForm({ defaultValues: async () => { const response = await fetch("/api") return await response.json() // return { firstName: '', lastName: '' } }, }) } ``` - 반응형 `values` ```javascript copy function App() { const { data } = useQuery() // data는 { firstName: '', lastName: '' }를 반환 const { register, handleSubmit } = useForm({ values: data, resetOptions: { keepDirtyValues: true, // 변경된 필드는 그대로 유지하되, defaultValues는 업데이트 }, }) } ``` --- ## 어떻게 ref의 사용을 공유하나요? {#Howtosharerefusage} React Hook Form은 입력 값을 수집하기 위해 `ref`가 필요합니다. 하지만 다른 목적(예: 뷰로 스크롤하거나 포커스)으로 `ref`를 사용하고 싶을 수도 있습니다. ```tsx import { useRef, useImperativeHandle } from "react" import { useForm } from "react-hook-form" type Inputs = { firstName: string lastName: string } export default function App() { const { register, handleSubmit } = useForm() const firstNameRef = useRef(null) const onSubmit = (data: Inputs) => console.log(data) const { ref, ...rest } = register("firstName") const onClick = () => { firstNameRef.current.value = "" } useImperativeHandle(ref, () => firstNameRef.current) return (
) } ``` ```javascript import { useRef, useImperativeHandle } from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit } = useForm() const firstNameRef = useRef(null) const onSubmit = (data) => console.log(data) const { ref, ...rest } = register("firstName") const onClick = () => { firstNameRef.current!.value = "" } useImperativeHandle(ref, () => firstNameRef.current) return (
) } ``` --- ## ref에 접근할 수 없는 경우는 어떻게 해야 하나요? {#Whatifyoudonthaveaccesstoref} 실제로 `ref` 없이도 입력을 `register`할 수 있습니다. 사실, 수동으로 `setValue`, `setError`, `trigger`를 사용할 수 있습니다. **참고:** `ref`가 등록되지 않았기 때문에 React Hook Form은 입력에 이벤트 리스너를 등록할 수 없습니다. 즉, 값과 에러를 수동으로 업데이트해야 합니다. ```javascript import React, { useEffect } from "react" import { useForm } from "react-hook-form" export default function App() { const { register, handleSubmit, setValue, setError } = useForm() const onSubmit = (data) => console.log(data) useEffect(() => { register("firstName", { required: true }) register("lastName") }, [register]) return (
setValue("firstName", e.target.value)} /> { const value = e.target.value if (value === "test") { setError("lastName", "notMatch") } else { setValue("lastName", e.target.value) } }} />
) } ``` --- ## 첫 번째 키 입력이 동작하지 않는 이유는 무엇인가요? {#Whyisthefirstkeystrokenotworking} `value`를 사용하지 않는지 확인하세요. 올바른 속성은 `defaultValue`입니다. React Hook Form은 비제어 입력에 중점을 두고 있으므로, `onChange`를 통해 `state`로 입력 `value`를 변경할 필요가 없습니다. 사실 `value`는 전혀 필요하지 않습니다. 초기 입력 값을 설정하기 위해 `defaultValue`만 설정하면 됩니다. --- ## React Hook Form, Formik 또는 Redux Form? {#ReactHookFormFormikorReduxForm} 우선, 모든 라이브러리는 동일한 문제를 해결하려고 합니다: 폼 구축 경험을 최대한 쉽게 만드는 것입니다. 하지만 이 세 가지 사이에는 몇 가지 근본적인 차이점이 있습니다. `react-hook-form`은 비제어 입력을 염두에 두고 만들어졌으며, 가능한 최고의 성능과 최소한의 리렌더링을 제공하려고 합니다. 또한 `react-hook-form`은 React Hook으로 만들어졌고 훅으로 사용되므로, 임포트할 컴포넌트가 없습니다. 다음은 세부적인 차이점입니다: | | React Hook Form | Formik | Redux Form | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | **컴포넌트** | [비제어](https://reactjs.org/docs/uncontrolled-components.html) & [제어](https://reactjs.org/docs/forms.html) | [제어](https://reactjs.org/docs/forms.html) | [제어](https://reactjs.org/docs/forms.html) | | **렌더링** | 최소 리렌더링 및 계산 최적화 | 로컬 상태 변경에 따라 리렌더링 (입력할 때마다) | 상태 관리 라이브러리(Redux) 변경에 따라 리렌더링 (입력할 때마다) | | **API** | Hooks | Component (RenderProps, Form, Field) + Hooks | Component (RenderProps, Form, Field) | | **패키지 크기** | 작음 `react-hook-form@7.27.0` **8.5KB** | 보통 `formik@2.1.4` **15KB** | 큼 `redux-form@8.3.6` **26.4KB** | | **유효성 검사** | 내장, [Yup](https://github.com/jquense/yup), [Zod](https://github.com/vriad/zod), [Joi](https://github.com/hapijs/joi), [Superstruct](https://github.com/ianstormtaylor/superstruct) 및 직접 구현 가능 | 직접 구현 또는 [Yup](https://github.com/jquense/yup) | 직접 구현 또는 플러그인 | | **학습 곡선** | 낮음에서 보통 | 보통 | 보통 | --- ## watch vs getValues vs state {#watchvsgetValuesvsstate} - **watch:** 이벤트 리스너를 통해 모든 입력 또는 특정 입력의 변경 사항을 구독하고, 구독된 필드에 따라 리렌더링합니다. 실제 동작은 [이 codesandbox](https://codesandbox.io/s/react-hook-form-watch-with-radio-buttons-and-select-examples-ovfus)를 확인하세요. - **getValues**: 커스텀 훅 내부에 참조로 저장된 값을 가져옵니다. 빠르고 가볍습니다. 이 메서드는 리렌더링을 트리거하지 않습니다. - **로컬 state**: React 로컬 상태는 단순히 입력의 상태뿐만 아니라 무엇을 렌더링할지도 결정합니다. 각 입력의 변경마다 트리거됩니다. --- ## 삼항 연산자로 기본값이 올바르게 변경되지 않는 이유는 무엇인가요? {#Whyisdefaultvaluenotchangingcorrectlywithternaryoperator} React Hook Form은 전체 폼과 입력을 제어하지 않기 때문에, React는 실제 입력이 교체되거나 바뀌었다는 것을 인식하지 못합니다. 해결 방법으로, 입력에 고유한 `key` prop을 부여하여 이 문제를 해결할 수 있습니다. key prop에 대한 자세한 내용은 [Kent C. Dodds가 작성한 이 글](https://kentcdodds.com/blog/understanding-reacts-key-prop)에서도 확인할 수 있습니다. ```javascript sandbox="https://codesandbox.io/s/react-hook-form-faq-toggle-fields-3htr6" copy import { useForm } from "react-hook-form" export default function App() { const { register } = useForm() return (
{watchChecked ? ( ) : ( )}
) } ``` --- ## 모달 또는 탭 폼과 함께 작업할 때는 어떻게 해야 하나요? {#Howtoworkwithmodalortabforms} React Hook Form이 각 입력 내부에 입력 상태를 저장하여 네이티브 폼 동작을 따른다는 것을 이해하는 것이 중요합니다(`useEffect`에서 커스텀 `register`를 사용하는 경우 제외). 흔한 오해는 입력 상태가 마운트되거나 언마운트된 입력에 유지된다고 생각하는 것입니다. 예를 들어 모달이나 탭 폼을 다룰 때 그렇습니다. 올바른 해결책은 각 모달이나 탭 내부에 새로운 폼을 만들고, 제출 데이터를 로컬 또는 전역 상태에 캡처한 다음 결합된 데이터로 작업하는 것입니다. - [모달 폼 및 토글 입력 예제](https://codesandbox.io/s/react-hook-form-modal-form-conditional-inputs-c7n0r) - [탭 폼 예제](https://codesandbox.io/s/tabs-760h9) 또는 `useForm`을 호출할 때 더 이상 사용되지 않는 옵션인 `shouldUnregister: false`를 사용할 수도 있습니다. ```javascript import { useForm, Controller } from "react-hook-form" function App() { const { control } = useForm() return ( } name="firstName" control={control} defaultValue="" /> ) } ``` ```javascript import React, { useEffect } from "react" import { useForm } from "react-hook-form" function App() { const { register, watch, setValue, handleSubmit } = useForm({ defaultValues: { firstName: "", lastName: "", }, }) const { firstName, lastName } = watch() useEffect(() => { register("firstName") register("lastName") }, [register]) const handleChange = (e, name) => { setValue(name, e.target.value) } const onSubmit = (data) => console.log(data) return (
handleChange(e, "firstName")} value={firstName} /> handleChange(e, "lastName")} value={lastName} />
) } ``` --- # Typescript Support > 내보내기(exported)한 Typescript 타입 목록입니다. **중요:** React Hook Form과 함께 사용하려면 TypeScript ^4.3 이상 버전을 권장합니다. ## \ Resolver {#Resolver} ```tsx import React from "react" import { useForm, Resolver } from "react-hook-form" type FormValues = { firstName: string lastName: string } const resolver: Resolver = async (values) => { return { values: values.firstName ? values : {}, errors: !values.firstName ? { firstName: { type: "required", message: "This is required.", }, } : {}, } } export default function App() { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver }) const onSubmit = handleSubmit((data) => console.log(data)) return (
{errors?.firstName &&

{errors.firstName.message}

}
) } ``` --- ## \ SubmitHandler {#SubmitHandler} ```tsx import React from "react" import { useForm, SubmitHandler } from "react-hook-form" type FormValues = { firstName: string lastName: string email: string } export default function App() { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) return (
) } ``` --- ## \ SubmitErrorHandler {#SubmitErrorHandler} ```tsx import React from "react" import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form" type FormValues = { firstName: string lastName: string email: string } export default function App() { const { register, handleSubmit } = useForm() const onSubmit: SubmitHandler = (data) => console.log(data) const onError: SubmitErrorHandler = (errors) => console.log(errors) return (
) } ``` --- ## \ Control {#Control} ```tsx import { useForm, useWatch, Control } from "react-hook-form" type FormValues = { firstName: string lastName: string } function IsolateReRender({ control }: { control: Control }) { const firstName = useWatch({ control, name: "firstName", defaultValue: "default", }) return
{firstName}
} export default function App() { const { register, control, handleSubmit } = useForm() const onSubmit = handleSubmit((data) => console.log(data)) return (
) } ``` --- ## \ UseFormReturn {#UseFormReturn} ```tsx export type UseFormReturn< TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues extends FieldValues | undefined = undefined > = { watch: UseFormWatch getValues: UseFormGetValues getFieldState: UseFormGetFieldState setError: UseFormSetError clearErrors: UseFormClearErrors setValue: UseFormSetValue trigger: UseFormTrigger formState: FormState resetField: UseFormResetField reset: UseFormReset handleSubmit: UseFormHandleSubmit unregister: UseFormUnregister control: Control register: UseFormRegister setFocus: UseFormSetFocus } ``` ```tsx import type { FieldValues, UseFormReturn, SubmitHandler } from "react-hook-form" import React from "react" import { useForm } from "react-hook-form" type InputProps = React.DetailedHTMLProps< React.InputHTMLAttributes, HTMLInputElement > const Input = React.forwardRef((props, ref) => ( )) type Option = { label: React.ReactNode value: string | number | string[] } type SelectProps = React.DetailedHTMLProps< React.SelectHTMLAttributes, HTMLSelectElement > & { options: Option[] } const Select = React.forwardRef( ({ options, ...props }, ref) => ( ) ) type FormProps = { onSubmit: SubmitHandler children: (methods: UseFormReturn) => React.ReactNode } const Form = ({ onSubmit, children, }: FormProps) => { const methods = useForm() return (
{children(methods)}
) } type FormValues = { firstName: string lastName: string sex: string } export default function App() { const onSubmit = (data: FormValues) => console.log(data) return ( onSubmit={onSubmit}> {({ register }) => ( <> )} ) } ``` --- ## \ UseFormProps {#UseFormProps} ```tsx export type UseFormProps< TFieldValues extends FieldValues = FieldValues, TContext extends object = object, TTransformedValues extends FieldValues | undefined = undefined > = Partial<{ mode: Mode disabled: boolean reValidateMode: Exclude defaultValues: DefaultValues | AsyncDefaultValues values: TFieldValues errors: FieldErrors resetOptions: Parameters>[1] resolver: Resolver context: TContext shouldFocusError: boolean shouldUnregister: boolean shouldUseNativeValidation: boolean progressive: boolean criteriaMode: CriteriaMode delayError: number }> ``` --- ## \ UseFieldArrayReturn {#UseFieldArrayReturn} ```tsx export type UseFieldArrayReturn< TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath = FieldArrayPath, TKeyName extends string = "id" > = { swap: UseFieldArraySwap move: UseFieldArrayMove prepend: UseFieldArrayPrepend append: UseFieldArrayAppend remove: UseFieldArrayRemove insert: UseFieldArrayInsert update: UseFieldArrayUpdate replace: UseFieldArrayReplace fields: FieldArrayWithId[] } ``` --- ## \ UseFieldArrayProps {#UseFieldArrayProps} ```typescript export type UseFieldArrayProps< TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath = FieldArrayPath, TKeyName extends string = 'id', > = { name: TFieldArrayName keyName?: TKeyName control?: Control rules?: { validate? | Validate[], TFieldValues> | Record< string, Validate[], TFieldValues> > } & Pick< RegisterOptions, 'maxLength' | 'minLength' | 'required' > shouldUnregister?: boolean } ``` --- ## \ UseControllerReturn {#UseControllerReturn} ```tsx export type UseControllerReturn< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > = { field: ControllerRenderProps formState: UseFormStateReturn fieldState: ControllerFieldState } ``` --- ## \ UseControllerProps {#UseControllerProps} ```tsx export type UseControllerProps< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath > = { name: TName rules?: Omit< RegisterOptions, "valueAsNumber" | "valueAsDate" | "setValueAs" | "disabled" > shouldUnregister?: boolean defaultValue?: FieldPathValue control?: Control disabled?: boolean } ``` --- ## \ FieldError {#FieldError} ```tsx export type FieldError = { type: LiteralUnion root?: FieldError ref?: Ref types?: MultipleFieldErrors message?: Message } ``` --- ## \ FieldErrors {#FieldErrors} ```tsx export type FieldErrors = Partial< FieldValues extends IsAny ? any : FieldErrorsImpl> > & { root?: Record & GlobalError } ``` --- ## \ Field {#Field} ```tsx export type Field = { _f: { ref: Ref name: InternalFieldName refs?: HTMLInputElement[] mount?: boolean } & RegisterOptions } ``` --- ## \ FieldPath {#FieldPath} 이 타입은 커스텀 컴포넌트의 `name` 속성을 정의할 때 유용하며, 필드 경로에 대해 타입 검사를 수행합니다. ```tsx export type FieldPath = Path ``` --- ## \ FieldPathByValue {#FieldPathByValue} 이 타입은 전달된 값과 일치하는 사용 가능한 모든 경로의 유니온 타입을 반환합니다. ```tsx export type FieldPathByValue = { [Key in FieldPath]: FieldPathValue< TFieldValues, Key > extends TValue ? Key : never }[FieldPath] ``` --- ## \ FieldValues {#FieldValues} ```tsx export type FieldValues = Record ``` --- ## \ FieldArrayWithId {#FieldArrayWithId} ```tsx export type FieldArrayWithId< TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath = FieldArrayPath, TKeyName extends string = "id" > = FieldArray & Record ``` --- ## \ Mode {#Mode} ```tsx export type ValidationMode = typeof VALIDATION_MODE export type Mode = keyof ValidationMode ``` --- ## \ RegisterOptions {#RegisterOptions} ```tsx export type RegisterOptions< TFieldValues extends FieldValues = FieldValues, TFieldName extends FieldPath = FieldPath > = Partial<{ required: Message | ValidationRule min: ValidationRule max: ValidationRule maxLength: ValidationRule minLength: ValidationRule validate: | Validate, TFieldValues> | Record< string, Validate, TFieldValues> > value: FieldPathValue setValueAs: (value: any) => any shouldUnregister?: boolean onChange?: (event: any) => void onBlur?: (event: any) => void disabled: boolean deps: FieldPath | FieldPath[] }> & ( | { pattern?: ValidationRule valueAsNumber?: false valueAsDate?: false } | { pattern?: undefined valueAsNumber?: false valueAsDate?: true } | { pattern?: undefined valueAsNumber?: true valueAsDate?: false } ) ``` --- ## \ FormStateProxy {#FormStateProxy} ```tsx export type FormStateProxy = { isDirty: boolean isValidating: boolean dirtyFields: FieldNamesMarkedBoolean touchedFields: FieldNamesMarkedBoolean validatingFields: FieldNamesMarkedBoolean errors: boolean isValid: boolean } ``` --- ## \ NestedValue (**Deprecated** at 7.33.0) {#NestedValue} ```tsx import React from "react" import { useForm, NestedValue } from "react-hook-form" import { TextField, Select } from "@material-ui/core" import { Autocomplete } from "@material-ui/lab" type Option = { label: string value: string } const options = [ { label: "Chocolate", value: "chocolate" }, { label: "Strawberry", value: "strawberry" }, { label: "Vanilla", value: "vanilla" }, ] export default function App() { const { register, handleSubmit, watch, setValue, formState: { errors }, } = useForm<{ autocomplete: NestedValue select: NestedValue }>({ defaultValues: { autocomplete: [], select: [] }, }) const onSubmit = handleSubmit((data) => console.log(data)) React.useEffect(() => { register("autocomplete", { validate: (value) => value.length || "This is required.", }) register("select", { validate: (value) => value.length || "This is required.", }) }, [register]) return (
option.label} onChange={(e, options) => setValue("autocomplete", options)} renderInput={(params) => ( )} /> ) } ``` ```tsx import { useForm, NestedValue } from "react-hook-form" type FormValues = { key1: string key2: number key3: NestedValue<{ key1: string key2: number }> key4: NestedValue } const { formState: { errors }, } = useForm() errors?.key1?.message // no type error errors?.key2?.message // no type error errors?.key3?.message // no type error errors?.key4?.message // no type error ```