TypeScript 지원
중요: React Hook Form을 사용하려면 TypeScript 4.1 이상이 필요합니다. 내보내는 여러 타입(재귀적 Path<T> 필드 이름 추론, 템플릿 리터럴 필드 경로)이 이 릴리스에서 도입된 제네릭 기능에 의존하므로 이전 버전에서는 타입 검사가 올바르게 작동하지 않을 수 있습니다. React Hook Form 자체는 TypeScript 6.x를 기준으로 개발하고 테스트합니다.
아래 타입 중 다수는 폼 값 타입(TFieldValues) 외에 두 가지 제네릭 매개변수를 공유합니다.
TContext—useForm의context옵션에 전달되는 두 번째 인수의 타입입니다. 변경 없이resolver로 전달되므로 검증 로직에서 폼 외부의 값(예: 로그인한 사용자, 기능 플래그)을 읽을 수 있습니다.TTransformedValues—resolver가 데이터를 변환한 후 생성하는 형태입니다(예:.transform()으로 문자열을Date로 바꾸는 Zod 스키마). 기본값은TFieldValues이며, 리졸버가 데이터를handleSubmit의onValid콜백에 전달하기 전에 형태를 변경할 때만 달라집니다.
</> Resolver
Resolver를 사용하면 React Hook Form의 내장 검증 대신 외부 검증 라이브러리(Zod, Yup, Joi 등)를 연결할 수 있습니다. 현재 폼 값을 받아 { values, errors }를 반환해야 합니다.
import { useForm, Resolver } from "react-hook-form"
type FormValues = {
firstName: string
lastName: string
}
const resolver: Resolver<FormValues> = 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<FormValues>({ resolver })
const onSubmit = handleSubmit((data) => console.log(data))
return (
<form onSubmit={onSubmit}>
<input {...register("firstName")} placeholder="Bill" />
{errors?.firstName && <p>{errors.firstName.message}</p>}
<input {...register("lastName")} placeholder="Luo" />
<input type="submit" />
</form>
)
}
</> SubmitHandler
import { useForm, SubmitHandler } from "react-hook-form"
type FormValues = {
firstName: string
lastName: string
email: string
}
export default function App() {
const { register, handleSubmit } = useForm<FormValues>()
const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<input {...register("lastName")} />
<input type="email" {...register("email")} />
<input type="submit" />
</form>
)
}
</> SubmitErrorHandler
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form"
type FormValues = {
firstName: string
lastName: string
email: string
}
export default function App() {
const { register, handleSubmit } = useForm<FormValues>()
const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)
const onError: SubmitErrorHandler<FormValues> = (errors) =>
console.log(errors)
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>
<input {...register("firstName", { required: true })} />
<input {...register("lastName", { minLength: 2 })} />
<input type="email" {...register("email")} />
<input type="submit" />
</form>
)
}
</> FormSubmitHandler
Form 컴포넌트의 onSubmit 속성을 위한 타입입니다. SubmitHandler와 달리 페이로드에 제출의 원시 FormData와 formDataJson이 포함됩니다.
export type FormSubmitHandler<TTransformedValues> = (payload: {
data: TTransformedValues
event?: React.BaseSyntheticEvent
formData: FormData
formDataJson: string
method?: "post" | "put" | "delete"
}) => unknown | Promise<unknown>
</> Control
import { useForm, useWatch, Control } from "react-hook-form"
type FormValues = {
firstName: string
lastName: string
}
function IsolateReRender({ control }: { control: Control<FormValues> }) {
const firstName = useWatch({
control,
name: "firstName",
defaultValue: "default",
})
return <div>{firstName}</div>
}
export default function App() {
const { register, control, handleSubmit } = useForm<FormValues>()
const onSubmit = handleSubmit((data) => console.log(data))
return (
<form onSubmit={onSubmit}>
<input {...register("firstName")} />
<input {...register("lastName")} />
<IsolateReRender control={control} />
<input type="submit" />
</form>
)
}
</> UseFormReturn
export type UseFormReturn<
TFieldValues extends FieldValues = FieldValues,
TContext = any,
TTransformedValues = TFieldValues,
> = {
watch: UseFormWatch<TFieldValues>
getValues: UseFormGetValues<TFieldValues>
getErrors: UseFormGetErrors<TFieldValues>
getFieldState: UseFormGetFieldState<TFieldValues>
setError: UseFormSetError<TFieldValues>
clearErrors: UseFormClearErrors<TFieldValues>
setValue: UseFormSetValue<TFieldValues>
setValues: UseFormSetValues<TFieldValues>
trigger: UseFormTrigger<TFieldValues>
formState: FormState<TFieldValues>
resetField: UseFormResetField<TFieldValues>
reset: UseFormReset<TFieldValues>
resetDefaultValues: UseFormResetDefaultValues<TFieldValues>
handleSubmit: UseFormHandleSubmit<TFieldValues, TTransformedValues>
unregister: UseFormUnregister<TFieldValues>
control: Control<TFieldValues, TContext, TTransformedValues>
register: UseFormRegister<TFieldValues>
setFocus: UseFormSetFocus<TFieldValues>
subscribe: UseFormSubscribe<TFieldValues>
}
import {
DetailedHTMLProps,
InputHTMLAttributes,
ReactNode,
SelectHTMLAttributes,
forwardRef,
} from "react"
import type { FieldValues, UseFormReturn, SubmitHandler } from "react-hook-form"
import { useForm } from "react-hook-form"
type InputProps = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
))
type Option = {
label: ReactNode
value: string | number | string[]
}
type SelectProps = DetailedHTMLProps<
SelectHTMLAttributes<HTMLSelectElement>,
HTMLSelectElement
> & { options: Option[] }
const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ options, ...props }, ref) => (
<select ref={ref} {...props}>
{options.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
)
)
type FormProps<TFormValues extends FieldValues> = {
onSubmit: SubmitHandler<TFormValues>
children: (methods: UseFormReturn<TFormValues>) => ReactNode
}
const Form = <TFormValues extends FieldValues>({
onSubmit,
children,
}: FormProps<TFormValues>) => {
const methods = useForm<TFormValues>()
return (
<form onSubmit={methods.handleSubmit(onSubmit)}>{children(methods)}</form>
)
}
type FormValues = {
firstName: string
lastName: string
sex: string
}
export default function App() {
const onSubmit = (data: FormValues) => console.log(data)
return (
<Form<FormValues> onSubmit={onSubmit}>
{({ register }) => (
<>
<Input {...register("firstName")} />
<Input {...register("lastName")} />
<Select
{...register("sex")}
options={[
{ label: "Female", value: "female" },
{ label: "Male", value: "male" },
{ label: "Other", value: "other" },
]}
/>
<Input type="submit" />
</>
)}
</Form>
)
}
</> UseFormProps
export type UseFormProps<
TFieldValues extends FieldValues = FieldValues,
TContext = any,
TTransformedValues = TFieldValues,
> = Partial<{
mode: Mode
disabled: boolean
reValidateMode: Exclude<Mode, "onTouched" | "all">
defaultValues: DefaultValues<TFieldValues> | AsyncDefaultValues<TFieldValues>
values: TFieldValues
errors: FieldErrors<TFieldValues>
resetOptions: Parameters<UseFormReset<TFieldValues>>[1]
resolver: Resolver<TFieldValues, TContext, TTransformedValues>
context: TContext
shouldFocusError: boolean
shouldUnregister: boolean
shouldUseNativeValidation: boolean
progressive: boolean
criteriaMode: CriteriaMode
delayError: number
formControl?: Omit<
UseFormReturn<TFieldValues, TContext, TTransformedValues>,
"formState"
>
validate: ValidateForm<TFieldValues>
}>
</> ValidateForm
useForm의 validate 옵션을 위한 타입입니다. resolver와 함께(또는 대신) 실행되는 폼 수준 검증입니다.
export type ValidateFormEventType =
| "blur"
| "focusout"
| "change"
| "submit"
| "trigger"
| "valid"
export type ValidateForm<TFormValues extends FieldValues> = (props: {
formValues: TFormValues
formState: FormState<TFormValues>
eventType?: ValidateFormEventType
name?: FieldPath<TFormValues> | FieldPath<TFormValues>[]
}) => FormValidateResult<TFormValues> | Promise<FormValidateResult<TFormValues>>
export type FormValidateResult<TFormValues> =
| Partial<
Record<
keyof TFormValues,
{ message: string | string[] | boolean | undefined; type: string }
>
>
| string
| boolean
유효한 폼에는 true를 반환합니다. string을 반환하면 formState.errors.form이 설정되고, 필드 이름을 키로 하는 객체를 반환하면 각 항목에 대해 formState.errors.form.<fieldName>이 설정됩니다.
</> UseFormRegister
register의 타입입니다.
export type UseFormRegister<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
name: TFieldName,
options?: RegisterOptions<TFieldValues, TFieldName>
) => UseFormRegisterReturn<TFieldName>
</> UseFormRegisterReturn
register를 호출할 때 반환되는 형태입니다. 사용자 정의 Input 컴포넌트가 {...register(name)}을 spread할 때 타입을 지정하는 데 유용합니다.
export type UseFormRegisterReturn<
TFieldName extends InternalFieldName = InternalFieldName,
> = {
onChange: ChangeHandler
onBlur: ChangeHandler
ref: RefCallBack
name: TFieldName
min?: string | number
max?: string | number
maxLength?: number
minLength?: number
pattern?: string
required?: boolean
disabled?: boolean
}
</> UseFormUnregister
unregister의 타입입니다.
export type UseFormUnregister<TFieldValues extends FieldValues> = (
name?:
| FieldPath<TFieldValues>
| FieldPath<TFieldValues>[]
| readonly FieldPath<TFieldValues>[],
options?: Omit<
KeepStateOptions,
| "keepIsSubmitted"
| "keepSubmitCount"
| "keepValues"
| "keepDefaultValues"
| "keepErrors"
> & { keepValue?: boolean; keepDefaultValue?: boolean; keepError?: boolean }
) => void
</> UseFormWatch
watch의 타입입니다. 오버로드되어 있어 인수 없이, 단일 필드 이름으로, 필드 이름 배열로, 또는 리렌더링 없이 구독하기 위한 콜백으로 호출할 수 있습니다.
export type UseFormWatch<TFieldValues extends FieldValues> = {
(): TFieldValues
<TFieldNames extends readonly FieldPath<TFieldValues>[]>(
names: readonly [...TFieldNames],
defaultValue?: DeepPartial<TFieldValues>
): FieldPathValues<TFieldValues, TFieldNames>
<TFieldName extends FieldPath<TFieldValues>>(
name: TFieldName,
defaultValue?: FieldPathValue<TFieldValues, TFieldName>
): FieldPathValue<TFieldValues, TFieldName>
(
callback: WatchObserver<TFieldValues>,
defaultValues?: DeepPartial<TFieldValues>
): Subscription
}
</> UseFormSubscribe
subscribe의 타입입니다. 컴포넌트 리렌더링을 실행하지 않고 formState 변경을 구독합니다.
export type UseFormSubscribe<TFieldValues extends FieldValues> = <
TFieldNames extends readonly FieldPath<TFieldValues>[],
>(payload: {
name?: readonly [...TFieldNames] | TFieldNames[number]
formState?: Partial<ReadFormState>
callback: (
data: Partial<FormState<TFieldValues>> & {
values: TFieldValues
name?: InternalFieldName
type?: EventType
}
) => void
exact?: boolean
}) => () => void
</> UseFormHandleSubmit
handleSubmit 함수의 타입입니다.
export type UseFormHandleSubmit<
TFieldValues extends FieldValues,
TTransformedValues = TFieldValues,
> = (
onValid: SubmitHandler<TTransformedValues>,
onInvalid?: SubmitErrorHandler<TFieldValues>
) => (e?: React.BaseSyntheticEvent) => Promise<void>
</> UseFormReset
reset 함수의 타입입니다.
export type UseFormReset<TFieldValues extends FieldValues> = (
values?:
| DefaultValues<TFieldValues>
| TFieldValues
| ResetAction<TFieldValues>,
keepStateOptions?: KeepStateOptions
) => void
</> UseFormResetField
resetField 함수의 타입입니다.
export type UseFormResetField<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
name: TFieldName,
options?: ResetFieldConfig<TFieldValues, TFieldName>
) => void
</> UseFormResetDefaultValues
resetDefaultValues 함수의 타입입니다.
export type UseFormResetDefaultValues<TFieldValues extends FieldValues> = (
values: DefaultValues<TFieldValues> | TFieldValues,
options?: Partial<Pick<KeepStateOptions, "keepDirty" | "keepIsValid">>
) => void
</> UseFormSetError
setError 함수의 타입입니다.
export type UseFormSetError<TFieldValues extends FieldValues> = (
name: FieldPath<TFieldValues> | ErrorNamespacePath,
error: ErrorOption,
options?: {
shouldFocus: boolean
}
) => void
</> UseFormClearErrors
clearErrors 함수의 타입입니다.
export type UseFormClearErrors<TFieldValues extends FieldValues> = (
name?:
| FieldPath<TFieldValues>
| FieldPath<TFieldValues>[]
| readonly FieldPath<TFieldValues>[]
| ErrorNamespacePath
) => void
</> UseFormSetValue
setValue 함수의 타입입니다.
export type UseFormSetValue<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
name: TFieldName,
value: FieldPathValue<TFieldValues, TFieldName>,
options?: SetValueConfig
) => void
</> UseFormSetValues
setValues 함수의 타입입니다.
export type UseFormSetValues<TFieldValues extends FieldValues> = (
value: Partial<TFieldValues> | ((formValues: TFieldValues) => TFieldValues),
options?: SetValueConfig
) => void
</> UseFormSetFocus
setFocus 함수의 타입입니다.
export type SetFocusOptions = Partial<{
shouldSelect: boolean
}>
export type UseFormSetFocus<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(
name: TFieldName,
options?: SetFocusOptions
) => void
</> UseFormGetValues
getValues 함수의 타입입니다. 오버로드되어 있어 인수 없이, 단일 필드 이름으로 또는 필드 이름 배열로 호출할 수 있습니다.
export type GetValuesConfig = {
dirtyFields?: boolean
touchedFields?: boolean
}
export type UseFormGetValues<TFieldValues extends FieldValues> = {
(name?: undefined, config?: GetValuesConfig): TFieldValues
<TFieldName extends FieldPath<TFieldValues>>(
name: TFieldName,
config?: GetValuesConfig
): FieldPathValue<TFieldValues, TFieldName>
<TFieldNames extends FieldPath<TFieldValues>[]>(
names: readonly [...TFieldNames],
config?: GetValuesConfig
): [...FieldPathValues<TFieldValues, TFieldNames>]
}
</> UseFormGetErrors
getErrors 함수의 타입입니다. 오버로드되어 있어 인수 없이, 단일 오류 경로로 또는 오류 경로 배열로 호출할 수 있습니다.
export type ErrorNamespacePath =
| "root"
| `root.${string}`
| "form"
| `form.${string}`
export type GetErrorsResult<
TFieldValues extends FieldValues,
TName extends FieldPath<TFieldValues> | ErrorNamespacePath,
> = TName extends "root"
? (Record<string, GlobalError> & GlobalError) | undefined
: TName extends ErrorNamespacePath
? GlobalError | undefined
: TName extends FieldPath<TFieldValues>
? FieldPathError<TFieldValues, TName> | undefined
: never
export type UseFormGetErrors<TFieldValues extends FieldValues> = {
(name?: undefined): FieldErrors<TFieldValues>
<TName extends FieldPath<TFieldValues> | ErrorNamespacePath>(
name: TName
): GetErrorsResult<TFieldValues, TName>
<TNames extends (FieldPath<TFieldValues> | ErrorNamespacePath)[]>(
names: readonly [...TNames]
): {
[K in keyof TNames]: GetErrorsResult<TFieldValues, TNames[K]>
}
}
</> UseFormGetFieldState
getFieldState 함수의 타입입니다.
export type UseFormGetFieldState<TFieldValues extends FieldValues> = <
TFieldName extends FieldPath<TFieldValues>,
>(
name: TFieldName,
formState?: FormState<TFieldValues>
) => {
invalid: boolean
isDirty: boolean
isTouched: boolean
isValidating: boolean
error?: FieldError
}
</> UseFormTrigger
trigger 함수의 타입입니다.
export type TriggerConfig = Partial<{
shouldFocus: boolean
shouldTouch: boolean
}>
export type UseFormTrigger<TFieldValues extends FieldValues> = (
name?:
| FieldPath<TFieldValues>
| FieldPath<TFieldValues>[]
| readonly FieldPath<TFieldValues>[],
options?: TriggerConfig
) => Promise<boolean>
</> UseWatchProps
useWatch 훅이 받는 속성입니다.
export type UseWatchProps<TFieldValues extends FieldValues = FieldValues> = {
defaultValue?: unknown
disabled?: boolean
name?:
| FieldPath<TFieldValues>
| FieldPath<TFieldValues>[]
| readonly FieldPath<TFieldValues>[]
control?: Control<TFieldValues>
exact?: boolean
compute?: (formValues: TFieldValues) => TFieldValues
}
</> WatchProps
Watch 렌더 속성 컴포넌트를 위한 속성입니다.
export type WatchProps<
TFieldName extends WatchName<TFieldValues>,
TFieldValues extends FieldValues = FieldValues,
TContext = any,
TTransformedValues = TFieldValues,
TComputeValue = undefined,
> = {
control?: Control<TFieldValues, TContext, TTransformedValues>
name?: TFieldName
disabled?: boolean
exact?: boolean
defaultValue?: WatchDefaultValue<TFieldName, TFieldValues>
compute?: (value: WatchValue<TFieldName, TFieldValues>) => TComputeValue
render: (
value: WatchRenderValue<TFieldName, TFieldValues, TComputeValue>
) => React.ReactNode | React.ReactNode[]
}
</> UseFormStateProps
useFormState 훅이 받는 속성입니다.
export type UseFormStateProps<
TFieldValues extends FieldValues,
TTransformedValues = TFieldValues,
> = Partial<{
control?: Control<TFieldValues, any, TTransformedValues>
disabled?: boolean
name?:
| FieldPath<TFieldValues>
| FieldPath<TFieldValues>[]
| readonly FieldPath<TFieldValues>[]
exact?: boolean
}>
</> FormStateProps
FormState 렌더 속성 컴포넌트를 위한 속성입니다.
export type FormStateProps<
TFieldValues extends FieldValues,
TTransformedValues = TFieldValues,
> = UseFormStateProps<TFieldValues, TTransformedValues> & {
render: (values: UseFormStateReturn<TFieldValues>) => React.ReactNode
}
</> FormProviderProps
export type FormProviderProps<
TFieldValues extends FieldValues = FieldValues,
TContext = any,
TTransformedValues = TFieldValues,
> = {
children: React.ReactNode | React.ReactNode[]
} & UseFormReturn<TFieldValues, TContext, TTransformedValues>
</> FormProps
export type FormProps<
TFieldValues extends FieldValues,
TTransformedValues = TFieldValues,
> = Omit<React.FormHTMLAttributes<HTMLFormElement>, "onError" | "onSubmit"> &
Partial<{
control: Control<TFieldValues, any, TTransformedValues>
headers: Record<string, string>
validateStatus: (status: number) => boolean
onError: (
payload:
| { response: Response; error?: undefined }
| { response?: undefined; error: unknown }
) => void
onSuccess: ({ response }: { response: Response }) => void
onSubmit: FormSubmitHandler<TTransformedValues>
method: "post" | "put" | "delete"
children: React.ReactNode | React.ReactNode[]
render: (props: {
submit: (e?: React.FormEvent) => void
}) => React.ReactNode | React.ReactNode[]
encType:
| "application/x-www-form-urlencoded"
| "multipart/form-data"
| "text/plain"
| "application/json"
}>
</> UseFieldArrayReturn
export type UseFieldArrayReturn<
TFieldValues extends FieldValues = FieldValues,
TFieldArrayName extends
FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,
TKeyName extends string = "id",
> = {
swap: UseFieldArraySwap
move: UseFieldArrayMove
prepend: UseFieldArrayPrepend<TFieldValues, TFieldArrayName>
append: UseFieldArrayAppend<TFieldValues, TFieldArrayName>
remove: UseFieldArrayRemove
insert: UseFieldArrayInsert<TFieldValues, TFieldArrayName>
update: UseFieldArrayUpdate<TFieldValues, TFieldArrayName>
replace: UseFieldArrayReplace<TFieldValues, TFieldArrayName>
fields: (FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName> & {
disabled?: boolean
})[]
}
</> FieldArrayMethodProps
append, prepend, insert 함수의 두 번째 인수로 받는 포커스 옵션입니다.
export type FieldArrayMethodProps = {
shouldFocus?: boolean
focusIndex?: number
focusName?: string
}
</> UseFieldArrayProps
export type UseFieldArrayProps<
TFieldValues extends FieldValues = FieldValues,
TFieldArrayName extends
FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,
TKeyName extends string = "id",
TTransformedValues = TFieldValues,
> = {
name: TFieldArrayName
keyName?: TKeyName
control?: Control<TFieldValues, any, TTransformedValues>
rules?: {
validate?:
| Validate<FieldArray<TFieldValues, TFieldArrayName>[], TFieldValues>
| Record<
string,
Validate<FieldArray<TFieldValues, TFieldArrayName>[], TFieldValues>
>
} & Pick<
RegisterOptions<TFieldValues>,
"maxLength" | "minLength" | "required"
>
shouldUnregister?: boolean
disabled?: boolean
}
</> FieldArrayProps
export type FieldArrayProps<
TFieldValues extends FieldValues = FieldValues,
TFieldArrayName extends
FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,
TKeyName extends string = "id",
> = {
render: (
fieldArray: UseFieldArrayReturn<TFieldValues, TFieldArrayName, TKeyName>
) => React.ReactElement
} & UseFieldArrayProps<TFieldValues, TFieldArrayName, TKeyName>
</> ControllerRenderProps
field 객체가 Controller의 render 속성에 전달될 때의 형태입니다. 사용자 정의 제어 입력 컴포넌트의 타입을 지정할 때 유용합니다.
export type ControllerRenderProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
onChange: (...event: any[]) => void
onBlur: Noop
value: FieldPathValue<TFieldValues, TName>
disabled?: boolean
name: TName
ref: RefCallBack
}
</> ControllerFieldState
fieldState 객체가 Controller의 render 속성에 전달될 때의 형태입니다.
export type ControllerFieldState = {
invalid: boolean
isTouched: boolean
isDirty: boolean
isValidating: boolean
error?: FieldError
}
</> UseControllerReturn
export type UseControllerReturn<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
field: ControllerRenderProps<TFieldValues, TName>
formState: UseFormStateReturn<TFieldValues>
fieldState: ControllerFieldState
}
</> UseControllerProps
export type UseControllerProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TTransformedValues = TFieldValues,
> = {
name: TName
rules?: Omit<
RegisterOptions<TFieldValues, TName>,
"valueAsNumber" | "valueAsDate" | "setValueAs" | "disabled"
>
shouldUnregister?: boolean
defaultValue?: FieldPathValue<TFieldValues, TName>
control?: Control<TFieldValues, any, TTransformedValues>
disabled?: boolean
exact?: boolean
}
</> ControllerProps
export type ControllerProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TTransformedValues = TFieldValues,
> = {
render: ({
field,
fieldState,
formState,
}: {
field: ControllerRenderProps<TFieldValues, TName>
fieldState: ControllerFieldState
formState: UseFormStateReturn<TFieldValues>
}) => React.ReactElement
} & UseControllerProps<TFieldValues, TName, TTransformedValues>
</> FieldError
export type FieldError = {
type: LiteralUnion<keyof RegisterOptions, string>
root?: FieldError
ref?: Ref
types?: MultipleFieldErrors
message?: Message
}
</> FieldErrors
export type FieldErrors<T extends FieldValues = FieldValues> = Partial<
FieldErrorsImpl<DeepRequired<T>>
> & {
root?: Record<string, GlobalError> & GlobalError
form?: GlobalError
}
</> ErrorOption
setError가 받는 오류 객체입니다.
export type ErrorOption = {
message?: Message
type?: LiteralUnion<keyof RegisterOptions, string>
types?: MultipleFieldErrors
}
</> Field
export type Field = {
_f: {
ref: Ref
name: InternalFieldName
refs?: HTMLInputElement[]
mount?: boolean
} & RegisterOptions
}
</> FieldPath
사용자 정의 컴포넌트의 name 속성을 정의할 때 유용하며 필드 경로를 기준으로 타입을 검사합니다.
export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>
</> FieldPathByValue
이 타입은 전달된 값과 일치하는 모든 경로의 유니온을 반환합니다.
export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = {
[Key in FieldPath<TFieldValues>]: FieldPathValue<
TFieldValues,
Key
> extends TValue
? Key
: never
}[FieldPath<TFieldValues>]
</> FieldValues
export type FieldValues = Record<string, any>
</> FieldArrayWithId
export type FieldArrayWithId<
TFieldValues extends FieldValues = FieldValues,
TFieldArrayName extends
FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,
TKeyName extends string = "id",
> = FieldArray<TFieldValues, TFieldArrayName> & Record<TKeyName, string>
</> Mode
export type ValidationMode = typeof VALIDATION_MODE
export type Mode = keyof ValidationMode
</> RegisterOptions
export type RegisterOptions<
TFieldValues extends FieldValues = FieldValues,
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = Partial<{
required: Message | ValidationRule<boolean>
min: ValidationRule<number | string>
max: ValidationRule<number | string>
maxLength: ValidationRule<number>
minLength: ValidationRule<number>
validate:
| Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>
| Record<
string,
Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>
>
value: FieldPathValue<TFieldValues, TFieldName>
setValueAs: (value: any) => any
shouldUnregister?: boolean
onChange?: (event: any) => void
onBlur?: (event: any) => void
disabled: boolean
deps: FieldPath<TFieldValues> | FieldPath<TFieldValues>[]
}> &
(
| {
pattern?: ValidationRule<RegExp>
valueAsNumber?: false
valueAsDate?: false
}
| {
pattern?: undefined
valueAsNumber?: false
valueAsDate?: true
}
| {
pattern?: undefined
valueAsNumber?: true
valueAsDate?: false
}
)
</> FormState
formState의 타입이며 useForm과 useFormState가 반환합니다.
export type FormState<TFieldValues extends FieldValues> = {
isDirty: boolean
isLoading: boolean
isSubmitted: boolean
isSubmitSuccessful: boolean
isSubmitting: boolean
isValidating: boolean
isValid: boolean
disabled: boolean
submitCount: number
defaultValues?: undefined | Readonly<DeepPartial<TFieldValues>>
dirtyFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>
touchedFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>
validatingFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>
errors: FieldErrors<TFieldValues>
isReady: boolean
}
</> KeepStateOptions
reset과 useForm의 resetOptions가 받는 옵션입니다. 초기화 후에도 유지할 폼 상태 부분을 제어합니다.
export type KeepStateOptions = Partial<{
keepDirtyValues: boolean
keepErrors: boolean
keepDirty: boolean
keepValues: boolean
keepDefaultValues: boolean
keepIsSubmitted: boolean
keepIsSubmitSuccessful: boolean
keepTouched: boolean
keepIsValidating: boolean
keepIsValid: boolean
keepSubmitCount: boolean
keepFieldsRef: boolean
}>
</> FormStateProxy
export type FormStateProxy<TFieldValues extends FieldValues = FieldValues> = {
isDirty: boolean
isValidating: boolean
dirtyFields: FieldNamesMarkedBoolean<TFieldValues>
touchedFields: FieldNamesMarkedBoolean<TFieldValues>
validatingFields: FieldNamesMarkedBoolean<TFieldValues>
errors: boolean
isValid: boolean
}
</> OpaqueTypes
재귀 타입 헬퍼(Path, DeepPartial, FieldErrors 등)가 속성 내부로 재귀하는 대신 불투명한 리프 값으로 취급할 타입의 레지스트리입니다. 기본적으로 비어 있으며 선언 병합을 통해 타입을 등록하기 전에는 아무런 효과가 없습니다.
export interface OpaqueTypes {}
export type OpaqueType = OpaqueTypes[keyof OpaqueTypes]
Day.js의 Dayjs, Decimal.js의 Decimal, Luxon의 DateTime 같은 풍부한 서드파티 값 타입은 OpaqueTypes를 .d.ts 파일(또는 컴파일러에 모듈이 보이는 위치)에서 확장해 등록합니다.
import type { Dayjs } from "dayjs"
declare module "react-hook-form" {
interface OpaqueTypes {
dayjs: Dayjs
}
}
등록하면 Dayjs 타입의 필드(또는 구조적으로 이를 포함하는 타입)는 폼 값에서 얼마나 깊게 중첩되어 있든 React Hook Form의 내부 타입 헬퍼에서 리프로 취급됩니다.
장점
타입을 등록하지 않으면 Path<T>와 DeepPartial<T> 같은 헬퍼가 TFieldValues에 있는 모든 객체의 모든 속성으로 재귀하며, 여기에는 Dayjs나 Decimal 같은 서드파티 클래스 내부도 포함됩니다. 크거나 깊게 중첩된 폼에서는 이 재귀 비용 때문에 TypeScript가 "excessively deep" 복잡도 진단을 출력하거나 편집기의 타입 검사가 느려질 수 있습니다.
OpaqueTypes에 타입을 등록하면 해당 타입에서 멈추고 순회할 객체가 아닌 단일 리프 값으로 취급하도록 헬퍼에 알립니다. 내부에서 이미 Date와 FileList를 처리하는 방식과 같습니다. 따라서 단순한 원시 값 대신 풍부한 객체를 저장하는 폼에서도 필드 경로 추론과 다른 제네릭 타입 계산을 빠르고 정확하게 유지할 수 있습니다.
</> NestedValue (7.33.0에서 지원 중단 예정)
NestedValue는 지원 중단 예정이며 향후 메이저 버전에서 제거됩니다. 아래 배열 값처럼
제어 상태를 유지해야 하는 원시 값이 아닌 값에는 폼 값에서 필드 타입을 일반적으로 지정하고
Controller에 연결합니다. 타입을
NestedValue로 감싸지 않습니다.
import { useEffect } from "react"
import { useForm, NestedValue } from "react-hook-form"
import { TextField, Select, MenuItem } from "@mui/material"
import { Autocomplete } from "@mui/material"
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<Option[]>
select: NestedValue<number[]>
}>({
defaultValues: { autocomplete: [], select: [] },
})
const onSubmit = handleSubmit((data) => console.log(data))
useEffect(() => {
register("autocomplete", {
validate: (value) => value.length || "This is required.",
})
register("select", {
validate: (value) => value.length || "This is required.",
})
}, [register])
return (
<form onSubmit={onSubmit}>
<Autocomplete
options={options}
getOptionLabel={(option: Option) => option.label}
onChange={(e, options) => setValue("autocomplete", options)}
renderInput={(params) => (
<TextField
{...params}
error={Boolean(errors?.autocomplete)}
helperText={errors?.autocomplete?.message}
/>
)}
/>
<Select
value=""
multiple
// cast required because NestedValue<number[]> bypasses field-level type inference
onChange={(e) => setValue("select", e.target.value as number[])}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
</Select>
<input type="submit" />
</form>
)
}
import { useForm, NestedValue } from "react-hook-form"
type FormValues = {
key1: string
key2: number
key3: NestedValue<{
key1: string
key2: number
}>
key4: NestedValue<string[]>
}
const {
formState: { errors },
} = useForm<FormValues>()
errors?.key1?.message // no type error
errors?.key2?.message // no type error
errors?.key3?.message // no type error
errors?.key4?.message // no type error