์œ ์šฉํ•˜๊ฒŒ form๐Ÿ“ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” React Hook Form ํ•„์š”ํ•œ๋งŒํผ ์‹น ์ •๋ฆฌํ•ด๋ดค๋‹ค

ํ˜…ยท2024๋…„ 9์›” 26์ผ

ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „๋ถ€ํ„ฐ ๋‹ค์Œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๊ผญ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋˜ ์Šคํƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ React Hook Form์ด์—ˆ๋‹ค. ์ด์ „ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์›น ํ‘œ์ค€ ๋“ฑ์„ ์ „ํ˜€ ์ƒ๊ฐํ•˜์ง€ ์•Š๊ณ  ๊ตฌํ˜„ ํ•˜๋‚˜๋งŒ์„ ๋ชฉํ‘œ๋กœ ๋‘์—ˆ๋Š”๋ฐ ๊ทธ ์ค‘์—์„œ ์ œ์ผ ๊ตฌํ˜„๋งŒ ์ƒ๊ฐํ–ˆ๋‹ค๋ผ๊ณ  ๋А๊ปด์งˆ ๋ฒ•ํ•œ ๋ถ€๋ถ„์ด form๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์ด์—ˆ๋‹ค. form ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  input/button ๋งŒ์„ ์‚ฌ์šฉํ•ด์„œ form์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ฒŒ ํ–ˆ๋Š”๋ฐ, ์ดํ›„ form ํƒœ๊ทธ์— ๋Œ€ํ•ด์„œ ๊ณต๋ถ€๋ฅผ ํ•ด๋ณด๋‹ˆ ์ž˜๋ชป ๊ตฌํ˜„ํ–ˆ๋‹ค๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  form์— input๋“ค์ด ๋งŽ์•„์ง€๋ฉด์„œ useState ๋ณ€์ˆ˜๋“ค์„ ์ •๋ง ๋งŽ์ด ์„ ์–ธํ•˜๊ฒŒ ๋˜๊ณ  ๊ด€๋ฆฌ๋„ ๋ถˆํŽธํ•˜๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋˜ ์ค‘ ์•Œ๊ฒŒ ๋œ ๊ฒŒ React Hook Form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ form ๊ด€๋ฆฌ์— ์ข‹๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๊ฑฐ๊ธฐ๋‹ค๊ฐ€ React Hook Form์„ ๋งŽ์ด๋“ค ์‚ฌ์šฉํ•ด ๋ฐฐ์›Œ๋‘๋ฉด ์œ ์šฉํ•˜๊ฒ ๋‹ค ์‹ถ์—ˆ๋‹ค.

โ€ป ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ํ”„๋กœ์ ํŠธ์—์„œ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋“ค์€ ํ•˜๋‹จ์—์„œ ์ •๋ฆฌํ•˜๊ณ  React Hook Form ์‚ฌ์šฉ๋ฒ•์„ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋“ค์€ ์„ค๋ช…์„ ์œ„ํ•œ ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ๋กœ ์„ค๋ช…ํ•œ๋‹ค.

React Hook Form

์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด์ค€๋‹ค.

npm install react-hook-form

๋‹ค์Œ๊ณผ ๊ฐ™์ด useForm ํ›…์„ ํ†ตํ•ด ํ•„์š”ํ•œ ํ•จ์ˆ˜๋“ค์„ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ์•„๋ž˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ์ด๋‹ค. ์ฃผ์š” ํ•จ์ˆ˜๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ์•„๋ž˜์—์„œ ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค.

const { register, handleSubmit, formState: { errors } } = useForm();

register

ํผ ํ•„๋“œ๋ฅผ react hook form์˜ ๊ด€๋ฆฌ ํ•˜์— ๋“ฑ๋กํ•œ๋‹ค. ์ฆ‰, ๊ฐ ํผ ํ•„๋“œ์— register๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ ํ•˜๊ณ , ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

<input {...register("name", { required: true })} />

์ฒซ ๋ฒˆ์งธ ์ธ์ž("name"): ํ•„๋“œ ์ด๋ฆ„ ์ง€์ •
๋‘ ๋ฒˆ์งธ ์ธ์ž({ required: true, maxLength: 10 }): ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™

required: ํ•„์ˆ˜ ์ž…๋ ฅ ์—ฌ๋ถ€
min/max: ์ˆซ์žํ˜• ์ž…๋ ฅ์— ๋Œ€ํ•œ ์ตœ์†Œ/์ตœ๋Œ€๊ฐ’ ์„ค์ •
minLength/maxLength: ๋ฌธ์ž์—ด์˜ ์ตœ์†Œ/์ตœ๋Œ€ ๊ธธ์ด ์„ค์ •
pattern: ์ •๊ทœ์‹์„ ์ด์šฉํ•œ ์ž…๋ ฅ ํŒจํ„ด ์„ค์ •
validate: ์ปค์Šคํ…€ ๊ฒ€์ฆ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋™์ ์ธ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰

<input 
	type="password" 
    {...register("confirmPassword", { 
    	required: "Please confirm your password", 
    	validate: (value, { password }) => 
    		value === password || "Passwords do not match"
    })} 
    placeholder="Confirm Password" 
/>

๋‹ค์Œ๊ณผ ๊ฐ™์„ ๋•Œ confirmPassword๋Š” ํ•„์ˆ˜ ์ž…๋ ฅ ๊ฐ’์ด๊ณ  pass์™€ ๋™์ผํ•ด์•ผ ํ•œ๋‹ค๋Š” ์œ ํšจ์„ฑ ์กฐ๊ฑด์ด ์„ค์ •๋œ ๊ฒƒ์ด๋‹ค.

handleSubmit

ํผ์ด ์ œ์ถœ๋˜์—ˆ์„ ๋•Œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ์œ ํšจํ•˜๋ฉด ์ œ์ถœ ํ•จ์ˆ˜(์ฝœ๋ฐฑ ํ•จ์ˆ˜)๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

<form onSubmit={handleSubmit(onSubmit)}>
  {/* ํผ ๋‚ด์šฉ */}
</form>

์ฒซ ๋ฒˆ์งธ ์ธ์ž ๋ง๊ณ ๋„ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋ฅผ ๊ฐ€์ง€๋Š”๋ฐ ์ด๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ–ˆ์„ ๋•Œ ์‹คํ–‰๋  ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ด๋‹ค.

handleSubmit(onSubmit, onError);

formState

ํผ์˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ์ถ”์ ํ•œ๋‹ค. ํŠนํžˆ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค.

const { formState: { errors } } = useForm();

<input {...register("name", { required: true })} />
{errors.name && <p>This field is required</p>}

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ, errors.name ๊ฐ’์ด ์กด์žฌํ•  ๋•Œ(name์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ๋•Œ) ์˜ค๋ฅ˜ ๋ฌธ๊ตฌ๋ฅผ ๋ Œ๋”๋งํ•ด์ฃผ๊ฒŒ ๋œ๋‹ค.

errors: ํ•„๋“œ๋ณ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์˜ค๋ฅ˜๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฐ์ฒด

formState.errors; // { name: { type: "required", message: "Name is required" } }

isDirty: ํ•˜๋‚˜ ์ด์ƒ์˜ ํ•„๋“œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€
isLoading: ํผ ๋ฐ์ดํ„ฐ๋‚˜ ์˜ต์…˜์„ ๋น„๋™๊ธฐ๋กœ ๋กœ๋“œ ์ค‘์ธ์ง€ ์—ฌ๋ถ€
isSubmitting: ํผ์ด ์ œ์ถœ ์ค‘์ธ์ง€ ์—ฌ๋ถ€ (๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๋•Œ ํผ์ด ์ œ์ถœ ์ƒํƒœ์ž„์„ ํ‘œ์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ)
isSubmitted: ํผ์ด ํ•œ ๋ฒˆ์ด๋ผ๋„ ์ œ์ถœ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€
isSubmitSuccessful: ํผ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ œ์ถœ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€
submitCount: ํผ์ด ๋ช‡ ๋ฒˆ ์ œ์ถœ๋˜์—ˆ๋Š”์ง€๋ฅผ ์นด์šดํŠธ
isValid: ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์œ ํšจํ•œ์ง€ ์—ฌ๋ถ€
dirtyFields: ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ •ํ•œ ํ•„๋“œ๋“ค์„ ์ถ”์  (๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋“ค์„ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์ œ๊ณต)

formState.dirtyFields; // { name: true, email: true }

touchedFields: ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญ ๋˜๋Š” ํฌ์ปค์Šค๋ฅผ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์ค€ ํ•„๋“œ๋ฅผ ์ถ”์ 

formState.touchedFields; // { name: true, email: true }

defaultValues: useForm์—์„œ ์„ค์ •ํ•œ ํผ์˜ ์ดˆ๊ธฐ๊ฐ’

formState.defaultValues; // { name: "", email: "" }

reset

ํผ์˜ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

reset();  // ํผ์„ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ๋ฆฌ์…‹
reset({ name: "John" });  // ํŠน์ • ํ•„๋“œ์— ์ดˆ๊ธฐ๊ฐ’์„ ์ฃผ๊ณ  ๋ฆฌ์…‹

setValue

ํŠน์ • ํ•„๋“œ์˜ ๊ฐ’์„ ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

setValue("name", "John Doe");

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ธ ๋ฒˆ์งธ ์ธ์ž์— ์˜ต์…˜ ๊ฐ’์„ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค. shouldValidate๋Š” ๊ฐ’์„ ์„ค์ •ํ•  ๋•Œ ํ•ด๋‹น ํ•„๋“œ์— ๋Œ€ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ฆ‰์‹œ ์‹คํ–‰ํ•œ๋‹ค. shouldDirty๋Š” ๊ฐ’์„ ์„ค์ •ํ•  ๋•Œ ํ•ด๋‹น ํ•„๋“œ์˜ isDirty ์ƒํƒœ๊ฐ€ true๋กœ ์„ค์ •๋œ๋‹ค. ํผ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Œ์„ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์‹์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. shouldTouch๊ฐ’์„ ์„ค์ •ํ•  ๋•Œ ํ•ด๋‹น ํ•„๋“œ์˜ isTouched ์ƒํƒœ๊ฐ€ true๋กœ ์„ค์ •๋œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํผ ํ•„๋“œ๋ฅผ ์ƒํ˜ธ์ž‘์šฉํ–ˆ์Œ์„ ํ‘œ์‹œํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

setValue("email", "test@example.com", { shouldValidate: true });
setValue("name", "John Doe", { shouldDirty: true });
setValue("age", 25, { shouldTouch: true });

getValues

ํผ์˜ ํ˜„์žฌ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.

const values = getValues();  // ํผ์˜ ๋ชจ๋“  ํ•„๋“œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
const nameValue = getValues("name");  // ํŠน์ • ํ•„๋“œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ

watch

ํŠน์ • ํ•„๋“œ๋‚˜ ์ „์ฒด ํผ ๊ฐ’์˜ ๋ณ€๊ฒฝ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์‹œํ•œ๋‹ค.

const name = watch("name");  // "name" ํ•„๋“œ์˜ ๊ฐ’์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ์‹œ
const allValues = watch();  // ๋ชจ๋“  ํ•„๋“œ ๊ฐ’ ๊ฐ์‹œ

trigger

์ˆ˜๋™์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. ํŠน์ • ํ•„๋“œ๋‚˜ ์ „์ฒด ํ•„๋“œ๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠน์ • ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

trigger();  // ์ „์ฒด ํ•„๋“œ์— ๋Œ€ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
trigger("name");  // ํŠน์ • ํ•„๋“œ์— ๋Œ€ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

useForm ์˜ต์…˜

useForm์— ์˜ต์…˜์„ ์ „๋‹ฌํ•ด ํผ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์™€ ํผ ๋™์ž‘ ๋ฐฉ์‹์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

useForm({
  mode: 'onSubmit',   
  criteriaMode: 'all',   
  reValidateMode: 'onChange', 
  shouldFocusError: true   
});

defaultValues

ํผ์˜ ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค. ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ๋„ ์„ค์ • ๊ฐ€๋Šฅํ•˜๋‹ค.

const { register } = useForm({
  defaultValues: {
    name: "John Doe",
    email: "john@example.com",
  }
});
const { register, handleSubmit } = useForm({
    defaultValues: async () => {
      // ๋น„๋™๊ธฐ ์ž‘์—…์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด (Promise)
      const response = await fetch('https://api.example.com/user/1');
      const data = await response.json();

      // ๋น„๋™๊ธฐ ์ž‘์—… ํ›„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ defaultValues๋กœ ์„ค์ •
      return {
        name: data.name,
        email: data.email,
        age: data.age,
      };
    }
});

resolver

์™ธ๋ถ€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().positive().integer().required(),
});

const { register, handleSubmit, formState: { errors } } = useForm({
  resolver: yupResolver(schema)
});

context

ํผ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์™€ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ „์—ญ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ์ฃผ๋กœ resolver์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋œ๋‹ค.

const { register } = useForm({
  context: { isLoggedIn: true }
});

delayError

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์ „๊นŒ์ง€ ์ง€์—ฐ ์‹œ๊ฐ„์„ ์„ค์ •ํ•œ๋‹ค. ํ•„๋“œ ๊ฐ’์ด ๋ณ€๋™๋˜๋Š” ์ค‘๊ฐ„์—๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ”๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

useForm({
  delayError: 500 // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ 500ms ํ›„์— ํ‘œ์‹œ
});

shouldUseNativeValidation

HTML5์˜ ๊ธฐ๋ณธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ• ์ง€ ์—ฌ๋ถ€

useForm({
  shouldUseNativeValidation: true
});

criteriaMode

ํ•„๋“œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์—์„œ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ฒซ ๋ฒˆ์งธ ์—๋Ÿฌ๋งŒ ๋ฐ˜ํ™˜ํ• ์ง€, ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค. ('firstError' (default) | 'all')

mode

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์–ธ์ œ ์‹คํ–‰๋ ์ง€๋ฅผ ์„ค์ •ํ•œ๋‹ค. ('onSubmit' (default) | 'onBlur' | 'onChange' | 'onTouched' | 'all')

onSubmit: ํผ ์ œ์ถœ ์‹œ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰
-> ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์„ฑ๋Šฅ์ƒ ์œ ๋ฆฌ
onBlur: ํผ ํ•„๋“œ์—์„œ ํฌ์ปค์Šค๋ฅผ ์žƒ์„ ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ (์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ํด๋ฆญํ•œ ํ›„ ๋‹ค๋ฅธ ํ•„๋“œ๋กœ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํ–‰)
-> ํ•„๋“œ์˜ ์˜ค๋ฅ˜๋ฅผ ์ฆ‰์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆด ์ˆ˜ ์žˆ์Œ
onChange: ํผ ํ•„๋“œ์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰
-> ๋งŽ์€ ์ž…๋ ฅ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
onTouched: ํผ ํ•„๋“œ๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•œ ํ›„์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰
-> ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ณ ๋ คํ•˜์—ฌ, ์ž…๋ ฅ์ด ์™„๋ฃŒ๋œ ํ›„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜์—ฌ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณต
all: ๋ชจ๋“  ์ƒํƒœ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰

reValidateMode

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ•œ ํ›„ ์–ธ์ œ ๋‹ค์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค. ('onChange' (default) | 'onBlur')

shouldFocusError

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ•œ ํ•„๋“œ๋กœ ์ž๋™ ํฌ์ปค์Šค๋ฅผ ์ด๋™ํ• ์ง€ ์—ฌ๋ถ€

shouldUnregister

ํผ์—์„œ ํ•„๋“œ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ ํ•ด๋‹น ํ•„๋“œ์˜ ๊ฐ’์„ ํผ ์ƒํƒœ์—์„œ ์ œ๊ฑฐํ• ์ง€ ์—ฌ๋ถ€

keepDirtyOnReinitialize

ํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ ํ•„๋“œ์˜ dirty ์ƒํƒœ๋ฅผ ์œ ์ง€ํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.

useForm({
  defaultValues: { name: "John" },
  keepDirtyOnReinitialize: true
});

keepValues

ํผ ํ•„๋“œ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋œ ํ›„์—๋„ ๊ฐ’์„ ์œ ์ง€ํ• ์ง€ ์—ฌ๋ถ€

ํ”„๋กœ์ ํŠธ

๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๊ฐ€ ๋น„์Šทํ•˜๊ธฐ ๋•Œ๋ฌธ์— '๋‹‰๋„ค์ž„ input'์œผ๋กœ ์ •๋ฆฌํ•œ๋‹ค.

const rNickname = /^[a-zA-Z0-9๊ฐ€-ํžฃ]{2,8}$/;

<input
	type="text"
	className={`user__input mt-[4px] ${touchedFields.nickname && errors.nickname ? 'user__input__invalid'
    : touchedFields.nickname && !errors.nickname && nickname ? 'user__input__valid': ''}`}
	placeholder="๋‹‰๋„ค์ž„"
	aria-required="true"
	{...register('nickname', {
		required: '๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”',
		pattern: {
			value: rNickname,
			message: '์˜ฌ๋ฐ”๋ฅธ ๋‹‰๋„ค์ž„ ํ˜•์‹์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”',
		},
	})}
/>

ํ”„๋กœ์ ํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ง„ํ–‰ํ•ด์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค. ์ด๋•Œ onBlur์™€ onTouched ์ค‘์— ์–ด๋–ค ๊ฐ’์œผ๋กœ ํ•ด์•ผํ• ์ง€, ๋‘˜์ด ๋ฌด์Šจ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ๊ณ ๋ฏผํ–ˆ๋Š”๋ฐ onBlur๋Š” ํ•„๋“œ์—์„œ ๋‹ค๋ฅธ ํ•„๋“œ๋กœ ์ด๋™ํ•  ๋•Œ ์‹คํ–‰๋œ๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๊ทธ๋Ÿฐ์ง€ input์ด ํ•œ ๊ฐœ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์ž˜ ์ง„ํ–‰๋˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ onTouched ์†์„ฑ์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค.
์œ ํšจ์„ฑ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ๋•Œ input์˜ border์˜ ์ƒ‰์ƒ๋„ ๋ณ€๊ฒฝ๋˜๋„๋ก ๊ตฌํ˜„ํ•ด์ฃผ์—ˆ๋Š”๋ฐ, errors.nickname์ด true์ผ ๋•Œ border์˜ ์ƒ‰์ƒ์„ ๋นจ๊ฐ›๊ฒŒ ํ•ด์ฃผ์—ˆ๋”๋‹ˆ default border๊ฐ€ ๋นจ๊ฐ„์ƒ‰์ด ๋˜์—ˆ๋‹ค. required ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅ๋˜์ง€ ์•Š์•„์„œ ๊ฐ’์ด ์ œ๋Œ€๋กœ ์ž…๋ ฅ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ํ•ญ์ƒ ๋นจ๊ฐ„์ƒ‰์„ ํ‘œ์‹œํ–ˆ๋‹ค. ์ด๋Š” ์˜๋„์™€๋Š” ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ฑด์— touchedFields.nickname์„ ์ฃผ์–ด ํ•ด๋‹น ๊ฐ’์ด ํ•œ ๋ฒˆ์ด๋ผ๋„ ํด๋ฆญ/ํฌ์ปค์Šค๋˜์—ˆ์„ ๋•Œ๋ผ๋Š” ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.
์ถ”๊ฐ€๋กœ required ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์›น ์ ‘๊ทผ์„ฑ์„ ์œ„ํ•ด์„œ aria-required๋ฅผ true๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

<p
	className={`text-sm min-h-[20px] font-medium ${nickname && !errors.nickname ? 'text-blue--500' : 'text-red--500'}`}
	role="alert"
	aria-hidden={errors.nickname ? 'true' : 'false'}
>
	{touchedFields.nickname && errors.nickname
	? errors.nickname.message
	: touchedFields.nickname && !errors.nickname && nickname
	? '์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ๋‹‰๋„ค์ž„ ์ž…๋‹ˆ๋‹ค'
	: ''}
</p>

์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ๋•Œ ์˜ค๋ฅ˜ ๋ฌธ๊ตฌ๋„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ์˜ค๋ฅ˜ ๋ฌธ๊ตฌ๋„ ๋น„์Šทํ•œ ์กฐ๊ฑด์„ ๊ฐ€์ง„๋‹ค. ์ด๋•Œ ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅ๋˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ๋ฌธ๊ตฌ๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด watch๋ฅผ ์ด์šฉํ•ด nickname ๊ฐ’์ด ์กด์žฌํ•˜๋Š”์ง€๋„ ์ฒดํฌํ•ด์ฃผ์—ˆ๋‹ค. p์— ๋ฌธ๊ตฌ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด aria-hidden ์†์„ฑ๋„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. (์ง€๊ธˆ๋ณด๋‹ˆ ์กฐ๊ฑด์ด ์ด์ƒํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์ˆ˜์ •์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค.)

์‚ฌ์šฉ ํ›„๊ธฐ

onChange ๋“ฑ ๋งŽ์€ ๊ฒƒ๋“ค์ด ์ƒ๋žต๋˜๋Š” ๊ฒƒ ๊ฐ™๊ณ , ํผ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋„ ๊ฐ„๋‹จํ•ด์„œ ๋งค์šฐ ๋งŒ์กฑ์Šค๋Ÿฝ๋‹ค. ๋‹ค๋งŒ, ์›ํ•˜๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ต์ˆ™ํ•˜์ง€ ์•Š๊ณ  ์ž˜ ๋ชจ๋ฅด๋Š” ๊ฑธ ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ ์ผ๋‹จ border ์ฒ˜๋ฆฌ๋ผ๋˜์ง€ ์‹ค์‹œ๊ฐ„ ์˜ค๋ฅ˜ ํ‘œ์‹œ๋ฅผ ์‚ฌ์ดํŠธ์—์„œ ์ •๋ง ๋งŽ์ดํ•˜๋Š” ๊ฒƒ ๊ฐ™์€๋ฐ ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ค์› ๋‹ค. ๊ทธ๋ž˜๋„ ๊ตฌํ˜„ ๋‚œ์ด๋„๋„ ์–ด๋ ต์ง€ ์•Š๊ณ , ๊ด€๋ฆฌ๋„ ํŽธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ์—๋„ ์‚ฌ์šฉํ•  ๋ฒ•ํ•œ ๋А๋‚Œ? ์ผ๋‹จ ๋งŒ์กฑ!

์ฐธ๊ณ  ์ž๋ฃŒ

React Hook Form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ๋ฒ•

profile
Frontend๐Ÿ“

0๊ฐœ์˜ ๋Œ“๊ธ€