Skip to content

useForm

폼 유효성 검사를 위한 리액트 훅

</> useForm: UseFormProps

useForm은 폼을 쉽게 관리하기 위한 사용자 정의 훅입니다. 선택적 인자로 하나의 객체를 받습니다. 다음 예제에서는 모든 속성과 기본값을 함께 확인할 수 있습니다.

일반적인 속성:

OptionDescription
mode제출 유효성 검사 전략.
reValidateMode제출 유효성 검사 전략.
defaultValues폼의 기본 값.
values폼 값을 업데이트하기 위한 반응형 값.
errors폼 에러를 업데이트하기 위한 반응형 에러.
resetOptions새로운 폼 값을 업데이트할 때 폼 상태 업데이트를 초기화할 수 있는 옵션.
criteriaMode모든 유효성 검사 에러를 한 번에 노출하거나 하나씩 노출.
shouldFocusError내장된 포커스 관리를 활성화하거나 비활성화.
delayError에러가 즉시 나타나는 것을 지연.
shouldUseNativeValidation브라우저 내장 폼 제약 조건 API 사용.
shouldUnregister언마운트 후 입력의 등록 취소(unregister)를 활성화하거나 비활성화.

스키마 유효성 검사 속성:

OptionDescription
resolver선호하는 스키마 유효성 검사 라이브러리와 통합됩니다.
context스키마 유효성 검사를 위해 제공할 컨텍스트 객체입니다.

Props


mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit' React Native: compatible with Controller


이 옵션을 사용하면 사용자가 폼을 제출하기 전에 유효성 검사 전략을 세울 수 있습니다. 유효성 검사는 handleSubmit 함수를 호출하여 트리거되는 onSubmit 이벤트 동안 발생합니다.

NameTypeDescription
onSubmitstring유효성 검사는 submit 이벤트에서 트리거되며, 입력은 onChange 이벤트 리스너와 연결되어 자신을 재검증합니다.
onBlurstring유효성 검사는 blur 이벤트에서 트리거됩니다.
onChangestring유효성 검사는 각 입력에 대한 change 이벤트에서 트리거되어 여러 번의 리렌더링을 발생시킵니다. 경고: 이는 성능에 상당한 영향을 미칠 수 있습니다.
onTouchedstring유효성 검사는 처음에 첫 번째 blur 이벤트에서 트리거됩니다. 그 이후에는, 모든 change 이벤트에서 트리거됩니다.

참고: Controller와 함께 사용할 때는, render 속성에 onBlur를 연결해야 합니다.
allstring유효성 검사는 blurchange 이밴트에서 모두 트리거됩니다.

reValidateMode: onChange | onBlur | onSubmit = 'onChange' React Native: Custom register or using Controller


이 옵션을 사용하면 사용자가 폼을 제출한 (즉, onSubmit 이벤트와 handleSubmit 함수가 실행된 후) 에러가 있는 입력이 다시 검증될 때의 유효성 검사 전략을 세울 수 있습니다. 기본적으로 재검증은 입력 change 이벤트 중 발생합니다.

defaultValues: FieldValues | () => Promise<FieldValues>


defaultValues 속성은 전체 폼을 기본값으로 채워줍니다. 이는 기본값의 동기 및 비동기 할당을 모두 지원합니다. 입력의 기본값을 설정할 때 defaultValuedefaultChecked를 사용할 수 있지만 (공식 리액트 문서에 자세히 설명), 전체 폼에는 defaultValues를 사용하는 것이 좋습니다.

useForm({
defaultValues: {
firstName: '',
lastName: ''
}
})
// 기본값을 비동기적으로 설정
useForm({
defaultValues: async () => fetch('/api-endpoint');
})
RULES
  • undefined를 기본값으로 제공하면 제어 컴포넌트의 기본 상태와 충돌하므로, undefined는 기본값으로 제공하면 안됩니다.

  • defaultValues는 캐시됩니다. 이를 재설정하려면 reset API를 사용하세요.

  • 기본적으로 defaultValues는 submission 결과에 포함됩니다.

  • defaultValuesMomentLuxon과 같은 프로토타입 메서드를 포함하는 사용자 정의 객체는 사용하지 않는 것이 좋습니다.

  • 폼 데이터를 포함하는 다른 옵션도 있습니다:

    ;<input {...register("hidden", { value: "data" })} type="hidden" />
    // onSubmit 시 데이터 포함
    const onSubmit = (data) => {
    const output = {
    ...data,
    others: "others",
    }
    }

values: FieldValues


values 속성은 변경 사항에 반응하여 폼 값을 업데이트하며, 이는 폼이 외부 상태나 서버 데이터에 의해 업데이트되어야 할 때 유용합니다. values 속성은 defaultValues 속성을 덮어쓰지만, useFormresetOptions: { keepDefaultValues: true }가 설정되어 있는 경우 덮어쓰지 않습니다.

// 기본값을 동기적으로 설정
function App({ values }) {
useForm({
values, // values 속성이 업데이트될 때 업데이트
})
}
function App() {
const values = useFetch("/api")
useForm({
defaultValues: {
firstName: "",
lastName: "",
},
values, // values가 반환되면 업데이트
})
}

errors: FieldErrors


errors 속성은 변경 사항에 반응하여 서버 에러 상태를 업데이트합니다. 이는 폼이 외부 서버에서 반환된 에러에 의해 업데이트되어야 할 때 유용합니다.

function App() {
const { errors, data } = useFetch("/api")
useForm({
errors, // errors가 반환되면 업데이트
})
}

resetOptions: KeepStateOptions


이 속성은 값 업데이트 동작과 관련이 있습니다. valuesdefaultValues가 업데이트될 때, 내부적으로 reset API가 호출됩니다. valuesdefaultValues가 비동기적으로 업데이트된 후 원하는 동작을 지정하는 것이 중요합니다. 이 설정 옵션 자체는 reset 메서드의 옵션을 참조합니다.

// 기본적으로 비동기 value or defaultValues가 업데이트되면 폼 값이 리셋됩니다.
useForm({ values })
useForm({ defaultValues: async () => await fetch() })
// 동작을 설정할 수 있는 옵션들
// eg: 사용자가 상호작용한/변경된 값은 유지하고, 사용자 에러를 제거하지 않기를 원합니다.
useForm({
values,
resetOptions: {
keepDirtyValues: true, // 사용자가 상호작용한 입력은 유지됩니다.
keepErrors: true, // 값이 업데이트되더라도 입력 에러는 유지됩니다.
},
})

context: object


이 context 객체는 변경이 가능하며 resolver의 두번째 인자 또는 Yup 유효성 검사의 context 객체에 주입됩니다. CodeSandbox

criteriaMode: firstError | all


  • firstError (기본값)로 설정하면, 각 필드에서 첫 번째 에러만 수집됩니다.
  • all로 설정하면, 각 필드의 모든 에러가 수집됩니다.
CodeSandbox

shouldFocusError: boolean = true


true (기본값)로 설정하면, 사용자가 유효성 검사를 통과하지 못한 폼을 제출하는 경우 에러가 있는 첫 번째 필드가 포커스됩니다.

NOTE
  • ref와 등록(registered)된 필드만 동작합니다. 사용자 정의 등록(registered) 입력은 적용되지 않습니다. 예를 들어: register('test') // 는 동작하지 않습니다
  • 포커스 순서는 register 순서에 따라 결정됩니다.

delayError: number


이 설정은 에러 상태가 사용자에게 표시되는 것을 지정된 밀리초만큼 지연시킵니다. 만약 사용자가 에러가 발생한 입력을 수정하면 에러는 즉시 제거되며, 지연이 적용되지 않습니다. CodeSandbox

shouldUnregister: boolean = false


기본적으로 입력값은 입력이 제거되는 경우에도 유지됩니다. 그러나 shouldUnregistertrue로 설정하면 언마운트 시 입력을 등록 해제(unregister)할 수 있습니다.

  • 이는 하위 레벨(child-level) 설정을 오버라이드하는 전역 설정입니다. 독립적인 동작을 원한다면, useForm이 아닌 컴포넌트나 훅 레벨에서 설정해야 합니다.

  • 기본적으로 shouldUnregister: false의 경우, 언마운트된 필드는 내장된 유효성 검사를 받지 않습니다.

  • useForm 레벨에서 shouldUnregister를 true로 설정하면 , defaultValues는 제출 결과와 합쳐지지 않습니다.

  • shouldUnregister: true 로 설정하면 폼이 기본 HTML 폼과 더 유사하게 동작합니다.

    • 폼 값은 입력 자체에 저장됩니다.

    • 입력 필드를 언마운트하면 해당 값이 제거됩니다.

    • 숨겨진 데이터는 hidden 속성을 사용하여 저장해야 합니다.

    • 등록된(registered) 입력만 submission 데이터에 포함됩니다.

    • hook form이 입력 필드가 DOM에서 언마운트되었는지 확인하려면 언마운트된 입력 필드는 useForm 또는 useWatchuseEffect에서 알려야 합니다.

      const NotWork = () => {
      const [show, setShow] = React.useState(false)
      // ❌ 알림을 받지 못하면, unregister를 호출해야 함.
      return show && <input {...register("test")} />
      }
      const Work = ({ control }) => {
      const { show } = useWatch({ control })
      // ✅ useEffect에서 알려줌.
      return show && <input {...register("test1")} />
      }
      const App = () => {
      const [show, setShow] = React.useState(false)
      const { control } = useForm({ shouldUnregister: true })
      return (
      <div>
      // ✅ useForm의 useEffect에서 알려줌.
      {show && <input {...register("test2")} />}
      <NotWork />
      <Work control={control} />
      </div>
      )
      }
      ```

shouldUseNativeValidation: boolean = false


이 설정은 브라우저의 기본 유효성 검사를 활성화합니다. 또한 CSS 선택자 :valid:invalid를 활성화하여 입력 필드의 스타일링을 쉽게 할 수 있습니다. 클라이언트 측 유효성 검사가 비활성화된 경우에도 이러한 선택자를 사용할 수 있습니다.

  • reportValidity 실행은 에러 입력 필드에 포커스를 주기 때문에, 이 기능은 onSubmitonChange 모드에서만 동작합니다.
  • 각 등록된(registered) 필드의 유효성 검사 메시지는 브라우저에서 직접 표시되기 위해 문자열이어야 합니다.
  • 이 기능은 실제 DOM 참조와 연결된 register API와 useController/Controller 에서만 동작합니다.

Examples:


import { useForm } from "react-hook-form"
export default function App() {
const { register, handleSubmit } = useForm({
shouldUseNativeValidation: 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>
)
}

resolver: Resolver


이 함수는 Yup, Zod, Joi, Vest, Ajv 등과 같은 외부 유효성 검사 라이브러리를 사용할 수 있게 해줍니다. 목표는 원하는 유효성 검사 라이브러리를 원활하게 통합할 수 있도록 하는 것입니다. 라이브러리를 사용하지 않는 경우에는 언제든 직접 로직을 작성하여 폼의 유효성을 검사할 수 있습니다.

npm install @hookform/resolvers
Props

NameTypeDescription
valuesobject이 객체에는 전체 폼 값이 포함됩니다.
contextobject이 객체는 useForm 설정에 전달할 수 있는 context 객체입니다. 이는 렌더링을 할 때마다 변경될 수 있는 변경 가능한 객체입니다.
options
{
  "criteriaMode": "string",
  "fields": "object",
  "names": "string[]"
}
이 객체는 useForm에서 유효성 검사가 완료된 필드, 필드 이름 및 criteriaMode에 대한 정보를 포함하는 옵션 객체입니다.
RULES
  • 스키마 유효성 검사는 필드 수준의 에러 리포트에 중점을 둡니다. 상위 수준 (Parent-level)의 에러 검사는 바로 위 상위 부모로 제한되며, 그룹 체크박스와 같은 구성 요소에 적용됩니다.
  • 이 함수는 캐시됩니다.
  • 사용자가 상호작용을 하는 동안 하나의 필드에서만 입력의 유효성 검사가 다시 이뤄집니다. 라이브러리 자체에서 에러 객체를 평가하여 적절하게 리렌더링을 트리거합니다.
  • resolver는 내장된 유효성 검사 (e.g.: required, min, etc.)와 함께 사용할 수 없습니다.
  • 사용자 정의 resolver를 만들 때:
    • 반환하는 객체가 valueserrors 속성을 모두 포함하도록 해야 합니다. 이들의 기본값은 빈 객체여야 합니다. 예를 들어: {}.
    • 에러 객체의 키는 필드의 이름값과 일치해야 합니다.

Examples:


import React from "react"
import { useForm } from "react-hook-form"
import { yupResolver } from "@hookform/resolvers/yup"
import * as yup from "yup"
type Inputs = {
name: string
age: string
}
const schema = yup
.object()
.shape({
name: yup.string().required(),
age: yup.number().required(),
})
.required()
const App = () => {
const { register, handleSubmit } = useForm<Inputs>({
resolver: yupResolver(schema), // yup, joi 그리고 직접 작성한 유효성 검사 로직도 사용 가능.
})
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<Schema>({
resolver: zodResolver(schema),
})
const onSubmit = (data: Schema) => {
console.log(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} />
<input {...register("age", { valueAsNumber: true })} type="number" />
<input type="submit" />
</form>
)
}
import React from "react";
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 * 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 (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("username")} />
<input {...register("password")} />
<input type="submit" />
</form>
)
}
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<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>
)
}

더 필요하신가요? Resolver Documentation를 확인하세요.

TIP

다음 코드 스니펫을 통해 스키마를 디버그할 수 있습니다:

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)
}

Return


다음 목록에는 useForm이 반환하는 속성에 대한 레퍼런스가 포함되어 있습니다.

지원해 주셔서 감사합니다

프로젝트에서 React Hook Form이 유용하다고 생각하신다면, 스타를 눌러 지원해 주시길 부탁드립니다.