useForm
</> useForm: UseFormProps
useForm은 폼을 쉽게 관리하기 위한 사용자 정의 훅입니다. 하나의 객체를 선택적 인수로 받습니다. 다음 예제는 모든 속성과 기본값을 보여 줍니다.
일반 속성:
| 옵션 | 설명 |
|---|---|
| mode | 제출 전 동작을 위한 검증 전략입니다. |
| reValidateMode | 제출 후 동작을 위한 검증 전략입니다. |
| defaultValues | 폼의 기본값입니다. 이 값은 캐시됩니다. |
| values | 폼 값을 갱신할 반응형 값입니다. |
| errors | 서버에서 반환된 오류로 폼을 갱신합니다. ⚠ 중요: 무한 리렌더링을 방지하려면 errors 객체의 참조를 안정적으로 유지합니다. |
| resetOptions | 새 폼 값으로 갱신할 때 폼 상태를 초기화하는 옵션입니다. |
| criteriaMode | 모든 검증 오류를 표시하거나 한 번에 하나만 표시합니다. |
| shouldFocusError | 내장 포커스 관리를 활성화하거나 비활성화합니다. |
| delayError | 오류가 즉시 나타나지 않도록 지연합니다. |
| validate | 폼 수준 검증은 내장 검증 메서드로 제한됩니다. |
| shouldUseNativeValidation | 브라우저의 내장 폼 제약 조건 API를 사용합니다. |
| shouldUnregister | 언마운트 후 입력의 등록 해제를 활성화하거나 비활성화합니다. |
| progressive | 검증 속성(required, min, max 등)을 입력의 네이티브 HTML 속성으로 전달합니다. Form 컴포넌트의 점진적 향상을 활성화합니다(하이드레이션/JS 로드 전에도 사용할 수 있는 HTML). |
| disabled | 연결된 모든 입력과 함께 전체 폼을 비활성화합니다. |
| formControl | 미리 생성한 폼 control 객체(createFormControl에서 생성)를 제공하여 useForm이 내부에서 만들지 않도록 합니다. |
스키마 검증 속성:
| 옵션 | 설명 |
|---|---|
| resolver | 선호하는 스키마 검증 라이브러리와 통합합니다. |
| context | 스키마 검증에 제공할 context 객체입니다. |
속성
mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit' ! React Native: Controller와 호환
이 옵션으로 폼 제출 전의 검증 전략을 구성할 수 있습니다. 검증은 onSubmit 이벤트에서 실행되며, 이 이벤트는 handleSubmit 함수를 호출하면 발생합니다.
| 이름 | 타입 | 설명 |
|---|---|---|
| onSubmit | string | submit 이벤트에서 검증이 실행되며, 입력에는 자체 재검증을 위한 onChange 이벤트 리스너가 연결됩니다. |
| onBlur | string | blur 이벤트에서 검증이 실행됩니다. |
| onChange | string | 각 입력의 change 이벤트에서 검증이 실행되어 여러 번 리렌더링됩니다. 경고: 성능에 상당한 영향을 주는 경우가 많습니다. |
| onTouched | string | 첫 번째 blur 이벤트에서 최초 검증이 실행됩니다. 이후에는 모든 change 이벤트에서 실행됩니다.참고: Controller와 함께 사용할 때는 onBlur를 render 속성에 연결해야 합니다. |
| all | string | blur와 change 이벤트 모두에서 검증이 실행됩니다. |
reValidateMode: onChange | onBlur | onSubmit = 'onChange' ! React Native: 사용자 정의 register 또는 Controller 사용
이 옵션으로 폼 제출 후 오류가 있는 입력을 다시 검증하는 전략을 구성할 수 있습니다(onSubmit 이벤트가 발생하고 handleSubmit 함수가 실행된 후). 기본적으로 입력 변경 이벤트에서 재검증이 실행됩니다.
참고: v7.56.0부터 mode와 reValidateMode는 모두 반응형입니다. 폼 초기화 후에도 갱신할 수 있으며 새 전략은 이후 검증부터 적용됩니다.
defaultValues: FieldValues | () => Promise<FieldValues>
defaultValues 속성은 전체 폼을 기본값으로 채웁니다. 기본값을 동기 또는 비동기로 할당할 수 있습니다. defaultValue 또는 defaultChecked를 사용해 입력의 기본값을 설정할 수도 있지만(React 공식 문서 참고), 전체 폼에는 defaultValues를 사용하는 것이 권장됩니다.
useForm({
defaultValues: {
firstName: "",
lastName: "",
},
})
// set default value async
useForm({
defaultValues: async () => fetch("/api-endpoint").then((res) => res.json()),
})
values: FieldValues v7.41.0부터
values 속성은 변경에 반응해 폼 값을 갱신하므로 외부 상태나 서버 데이터로 폼을 갱신해야 할 때 유용합니다. values 속성은 defaultValues 속성을 덮어쓰지만, resetOptions: { keepDefaultValues: true }도 useForm에 설정한 경우는 예외입니다.
// set default value sync
function App({ values }) {
useForm({
values, // will get updated when values props updates
})
}
function App() {
const values = useFetch("/api")
useForm({
defaultValues: {
firstName: "",
lastName: "",
},
values, // will get updated once values returns
})
}
errors: FieldErrors v7.49.0부터
errors 속성은 변경에 반응해 서버 오류 상태를 갱신하므로 서버에서 반환한 오류로 폼을 갱신해야 할 때 유용합니다.
function App() {
const { errors, data } = useFetch("/api")
useForm({
errors, // will get updated once errors returns
})
}
resetOptions: KeepStateOptions v7.41.0부터
이 속성은 값 갱신 동작과 관련이 있습니다. values나 defaultValues가 갱신되면 내부적으로 reset API가 호출됩니다. values나 defaultValues가 비동기로 갱신된 이후 원하는 동작을 지정해야 합니다. 구성 옵션 자체는 reset 메서드의 옵션을 참조합니다.
// by default, an asynchronous update to values or defaultValues will reset the form values
useForm({ values })
useForm({ defaultValues: async () => await fetch() })
// options to configure the behavior
// eg: I want to keep user-interacted/dirty values and not remove any user errors
useForm({
values,
resetOptions: {
keepDirtyValues: true, // user-interacted input will be retained
keepErrors: true, // input errors will be retained with value update
},
})
context: object
이 context object는 변경할 수 있으며 resolver의 두 번째 인수 또는 Yup 검증의 context 객체에 주입됩니다. | CodeSandbox 열기 ↗ |
criteriaMode: firstError | all
| CodeSandbox 열기 ↗ |
shouldFocusError: boolean = true
true(기본값)로 설정하면 검증에 실패한 폼을 제출할 때 오류가 있는 첫 번째 필드에 포커스가 설정됩니다.
delayError: number v7.12.0부터
| 이 구성은 사용자에게 오류 상태를 표시하는 시점을 지정한 밀리초만큼 지연합니다. 사용자가 오류가 있는 입력을 수정하면 오류는 즉시 제거되고 지연은 적용되지 않습니다. | CodeSandbox 열기 ↗ |
validate: Function v7.72.0부터
이 예제는 React 애플리케이션에서 새 validate API를 useForm과 함께 사용해 폼 수준 검증을 수행하는 방법을 보여 줍니다.
validate 함수는 { formValues, formState, eventType, name }을 받고 다음 중 하나를 반환해야 합니다. true(유효), string(formState.errors.form에 표시) 또는 { [fieldName]: { type, message } } 객체(각 항목이 formState.errors.form.<fieldName>에 표시)입니다. 참고: validate는 resolver가 구성되어 있으면 실행되지 않습니다. resolver와 validate는 상호 배타적이며 둘 다 설정하면 resolver만 실행됩니다.
예제:
const {
register,
formState: { errors },
} = useForm({
validate: async ({ formValues }) => {
if (formValues.test1.length > formValues.test.length) {
return {
test: {
type: "formError",
message: "something is wrong here",
},
}
}
if (formValues.test === "test") {
return "direct error message"
}
return true
},
})
shouldUnregister: boolean = false
기본적으로 입력이 제거되어도 해당 값은 유지됩니다. 하지만 shouldUnregister를 true로 설정하면 언마운트할 때 입력을 unregister할 수 있습니다.
- 하위 수준 구성을 재정의하는 전역 구성입니다. 개별적으로 동작하게 하려면
useForm이 아니라 컴포넌트 또는 훅 수준에서 구성합니다. - 기본값인
shouldUnregister: false에서는 언마운트된 필드를 내장 검증으로 검증하지 않습니다. shouldUnregister를useForm수준에서 true로 설정하면 제출 결과에defaultValues가 병합되지 않습니다.shouldUnregister: true로 설정하면 폼이 네이티브 폼과 유사하게 동작합니다.-
폼 값은 입력 자체에 저장됩니다.
-
입력을 언마운트하면 해당 값이 제거됩니다.
-
숨겨진 데이터를 저장하는 입력에는
hidden속성을 사용해야 합니다. -
등록된 입력만 제출 데이터에 포함됩니다.
-
React Hook Form이 입력이 DOM에서 언마운트되었음을 확인할 수 있도록
useForm또는useWatch의useEffect에서 언마운트된 입력을 알려야 합니다.const NotWork = ({ register, show }) => {
// ❌ won't get notified; you need to invoke unregister
return show && <input {...register("test")} />
}
const Work = ({ control, register }) => {
// "show" is itself a registered checkbox field in the same form
const { show } = useWatch({ control })
// ✅ gets notified in useEffect
return show && <input {...register("test1")} />
}
const App = () => {
const [showNotWork, setShowNotWork] = useState(false)
const { register, control } = useForm({ shouldUnregister: true })
return (
<div>
<input type="checkbox" {...register("show")} />
{/* ✅ gets notified in useForm's useEffect */}
{showNotWork && <input {...register("test2")} />}
<NotWork register={register} show={showNotWork} />
<Work control={control} register={register} />
</div>
)
}
-
shouldUseNativeValidation: boolean = false v7.9.0부터
이 구성은 브라우저 네이티브 검증을 활성화합니다. CSS 선택자 :valid와 :invalid도 활성화되어 입력 스타일을 더 쉽게 지정할 수 있습니다. 클라이언트 측 검증이 비활성화되어 있어도 이 선택자를 사용할 수 있습니다.
-
onSubmit과onChange모드에서만 작동합니다.reportValidity가 실행되면 오류 입력에 포커스가 설정되기 때문입니다. -
네이티브로 표시하려면 등록된 각 필드의 검증 메시지가 문자열이어야 합니다.
-
이 기능은 실제 DOM 참조에 연결된
registerAPI와useController/Controller에서만 작동합니다. -
shouldUseNativeValidation은progressive와 독립적으로 작동합니다. 검증 속성이 네이티브 HTML 속성으로 전달되는지와 관계없이 React Hook Form이 계산한 검증 결과로 브라우저의 Constraint Validation API(setCustomValidity/reportValidity)를 직접 구동합니다.progressive와 함께 사용하면 SSR이나 하이드레이션 전 대체 검증 등을 위해 제약 조건 속성 자체(required,minLength등)도 DOM에 표시할 수 있습니다.useForm({
shouldUseNativeValidation: true,
})
예제:
import { useForm } from "react-hook-form"
export default function App() {
const { register, handleSubmit } = useForm({
shouldUseNativeValidation: true,
progressive: true,
})
const onSubmit = async (data) => {
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("firstName", {
required: "Please enter your first name.",
})} // custom message
/>
<input type="submit" />
</form>
)
}
progressive: boolean = false v7.44.0부터
검증 규칙(required, min, max, minLength, maxLength, pattern)을 register에서 입력의 네이티브 HTML 속성으로 전달합니다. Form 컴포넌트의 점진적 향상을 활성화하여 하이드레이션 전에도 사용할 수 있는 HTML을 렌더링합니다. 이 옵션과 관계없이 React Hook Form 자체의 검증 결과로 브라우저 Constraint Validation API를 구동하는 shouldUseNativeValidation과는 독립적입니다. 제약 조건 속성도 DOM에 표시하려면 두 옵션을 함께 사용합니다.
useForm({
progressive: true,
})
disabled: boolean = false v7.48.0부터
이 구성을 true로 설정하면 전체 폼과 연결된 모든 입력을 비활성화할 수 있습니다.
비동기 작업 중 사용자 상호작용을 방지하거나 입력이 일시적으로 반응하지 않아야 하는
상황에 유용합니다.
예제:
import { useForm, Controller } from "react-hook-form"
const App = () => {
const [disabled, setDisabled] = useState(false)
const { register, handleSubmit, control } = useForm({
disabled,
})
return (
<form
onSubmit={handleSubmit(async () => {
setDisabled(true)
await sleep(100)
setDisabled(false)
})}
>
<input
type={"checkbox"}
{...register("checkbox")}
data-testid={"checkbox"}
/>
<select {...register("select")} data-testid={"select"} />
<Controller
control={control}
render={({ field }) => <input disabled={field.disabled} />}
name="test"
/>
<button type="submit">Submit</button>
</form>
)
}
resolver: Resolver
이 함수로 Yup, Zod, Joi, Vest, Ajv 등 다양한 외부 검증 라이브러리를 사용할 수 있습니다. 선호하는 검증 라이브러리를 원활하게 통합하는 것이 목적입니다. 라이브러리를 사용하지 않는 경우에도 폼 검증 로직을 직접 작성할 수 있습니다.
npm install @hookform/resolvers
매개변수
| 이름 | 타입 | 설명 |
|---|---|---|
values | object | 전체 폼 값을 담은 객체입니다. |
context | object | context 객체이며 useForm 구성에 제공할 수 있습니다. 리렌더링할 때마다 변경할 수 있는 가변 object입니다. |
options | {
"criteriaMode": "string",
"fields": "object",
"names": "string[]"
} | 검증한 필드와 이름, criteriaMode 등 useForm의 정보를 담은 옵션 객체입니다. |
예제:
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 and even your own.
})
return (
<form onSubmit={handleSubmit((d) => console.log(d))}>
<input {...register("name")} />
<input type="number" {...register("age")} />
<input type="submit" />
</form>
)
}
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<typeof schema>
const App = () => {
const { register, handleSubmit } = useForm({
resolver: zodResolver(schema),
})
return (
<form
onSubmit={handleSubmit((data) => {
// handle inputs
console.log(data)
})}
>
<input {...register("name")} />
<input {...register("age", { valueAsNumber: true })} type="number" />
<input type="submit" />
</form>
)
}
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<IFormInput>({
resolver: joiResolver(schema),
})
const onSubmit = (data: IFormInput) => {
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} />
<input type="number" {...register("age")} />
<input type="submit" />
</form>
)
}
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 (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("username")} />
{errors.username && <p>{errors.username.message}</p>}
<input {...register("password")} />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">submit</button>
</form>
)
}
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 (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("username")} />
<input {...register("password")} />
<input type="submit" />
</form>
)
}
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<IFormInputs>({
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 (
<div className="App">
<h1>resolver</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<label>Username</label>
<input {...register("username")} />
{errors.username && <p>{errors.username.message}</p>}
<input type="submit" />
</form>
</div>
)
}
더 자세한 내용은 리졸버 문서를 참고하세요.
useForm 반환값과 useEffect 의존성
향후 메이저 릴리스에서는 useForm 반환값이 성능을 최적화하고 formState 변경을 반영하도록 메모이제이션됩니다. 이 변경이 적용되면 formState가 갱신될 때마다 반환 객체에 새 참조가 생깁니다. 따라서 이 객체를 useEffect 의존성 배열에 직접 넣으면 formState가 바뀔 때마다 effect가 다시 실행되며, effect 자체가 폼을 갱신하면(예: reset 호출) 무한 루프가 발생할 수 있습니다.
아래와 같이 관련 메서드만 전달하면 이러한 문제를 피할 수 있습니다.
const methods = useForm()
useEffect(() => {
methods.reset({ ... })
}, [methods.reset])
반환값
다음 목록은 useForm이 반환하는 속성의 참조 문서입니다.