๐Ÿ“– TIL - ํ”„๋กœ์ ํŠธ 4์ผ์ฐจ: React Hook Form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ „ ๋„์ž…๊ณผ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… ๊ธฐ๋ก

์Š˜ยท2025๋…„ 2์›” 16์ผ

๐Ÿ“– TIL

๋ชฉ๋ก ๋ณด๊ธฐ
58/90

๐Ÿค” ๋„์ž… ์ „: ๊ธฐ์กด ํผ ๊ด€๋ฆฌ ๋ฐฉ์‹ ์ดํ•ดํ•˜๊ธฐ

์ œ์–ด ์ปดํฌ๋„ŒํŠธ (Controlled Component)

// ๊ธฐ์กด ์ฝ”๋“œ
const [addFeedData, setAddFeedData] = useState(INITIAL_ADD_FEED_DATA);

const handleInputChange = (e, field) => {
  const { value } = e.target;
  setAddFeedData(state => ({
    ...state,
    [field]: value,
  }));
};

๋งค ์ž…๋ ฅ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ฐ›์•„์˜ค๋Š” ๋น„ํšจ์œจ์ ์ธ ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค.
์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž์‹ ์˜ state๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. React์—์„œ๋Š” ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” state๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ์˜ state ์†์„ฑ์— ์œ ์ง€๋˜๋ฉฐ setState()์— ์˜ํ•ด ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.
ํผ์„ ๋ Œ๋”๋งํ•˜๋Š” React ์ปดํฌ๋„ŒํŠธ๋Š” ํผ์— ๋ฐœ์ƒํ•˜๋Š” ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ React์— ์˜ํ•ด ๊ฐ’์ด ์ œ์–ด๋˜๋Š” ์ž…๋ ฅ ํผ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ โ€œ์ œ์–ด ์ปดํฌ๋„ŒํŠธ (controlled component)โ€œ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

useState๋ฅผ ์‚ฌ์šฉํ• ๋•Œ onChange์—์„œ ๋ฐ›์•„์˜ค๋Š” ๊ฐ’์€ ๋น„ํšจ์œจ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜ํ•˜๋‚˜ ๊ฐ’์„ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์†๋„ ์—ญ์‹œ ๋А๋ฆด ์ˆ˜ ๋ฐ–์— ์—†๋Š” ๋‹จ์ ์ด ์กด์žฌํ•  ๊ฒƒ์ด๊ธฐ์—..

์‹ค์ œ๋กœ useState์—์„œ input๊ฐ’์„ onChange๋กœ ๋ฐ›์•„์˜ค๊ฒŒ ๋œ๋‹ค๋ฉด...

ex) ๊ฐ€๋‚˜๋‹ค -> ใ„ฑ, ๊ฐ€, ๊ฐ€ใ„ด, ๊ฐ€๋‚˜, ๊ฐ€๋‚˜ใ„ท, ๊ฐ€๋‚˜๋‹ค

์™€ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ๋„ ๊ณ„์†์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ฐ›์•„์˜ค๋ฏ€๋กœ ๋น„ํšจ์œจ์ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ (UnControlled Component)

๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ๋กœ ๋“ค๋งŒํ•œ๊ฒƒ์€ useRef()๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š”๋ฐ์š”. ref๋Š” ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•˜์—ฌ๋„ ๋ฆฌ๋ Œ๋”๋ง ๋˜์ง€์•Š๋Š” ํŠน์„ฑ์œผ๋กœ, ์ž…๋ ฅ์ด ๋ชจ๋‘ ๋˜๊ณ  ๋‚œ ํ›„ ref๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์™€์„œ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. state๋กœ ๊ฐ’์„ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ’์ด ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง์„ ํ•˜์ง€ ์•Š๊ณ  ๊ฐ’์„ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์„ฑ๋Šฅ์ƒ์˜ ์ด์ ์ด ์žˆ์œผ๋‚˜, ๋ฐ์ดํ„ฐ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋Š” ๋‹จ์ ์ด ์žˆ๊ฒ ์ฃ ..

๊ทธ๋Ÿผ ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์™ธ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด ๋ณผ ์ˆ˜๋Š” ์—†์„๊นŒ?
๋งˆ์นจ ๊ทธ๊ฒƒ๊ณผ ๊ฒธํ•ด์„œ ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ จ ์ •๋ณด๋ฅผ ์„œ์น˜ํ•ด๋ณด์•˜๋‹ค. ํ˜น์‹œ ๋ฆฌ์•กํŠธ์—์„œ ๊ถŒ์žฅํ•˜๊ณ  ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ์—†์„๊นŒ?

๐Ÿงš๐Ÿป ์•ˆ๋…•, ๋‚˜๋Š” Reactย Hookย Form

React Hook์„ ์‚ฌ์šฉํ•˜์—ฌ ํผ ๊ด€๋ฆฌ์™€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‹ค์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค

๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ

react-hook-form์€ ๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ๋กœ ๋ Œ๋”๋ง์„ ์ตœ์ ํ™” ํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๊ณ  ํ•˜๋„ค์š”?

๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ

import { useForm } from "react-hook-form";

const {
    handleSubmit, // form onSubmit์— ๋“ค์–ด๊ฐ€๋Š” ํ•จ์ˆ˜
    register, // onChange ๋“ฑ์˜ ์ด๋ฒคํŠธ ๊ฐ์ฒด ์ƒ์„ฑ
    watch, // register๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ๋ชจ๋“  ๊ฐ’ ํ™•์ธ
    formState: { errors }, // errors: register์˜ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ ์ž๋™ ์ถœ๋ ฅ
  } = useForm();

์‚ฌ์šฉ๋ฒ•

  1. ๊ธฐ๋ณธ ์„ธํŒ…:
import { useForm } from "react-hook-form";

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

  const onSubmit = (data) => {
    console.log(data); // ํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์—ฌ๊ธฐ๋กœ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("์ด๋ฆ„")} />
      <button type="submit">์ œ์ถœ</button>
    </form>
  );
}
  1. register๋ž€?
  • input ํ•„๋“œ๋ฅผ React Hook Form์— ๋“ฑ๋กํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค
  • ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด form์˜ validation๊ณผ ๊ฐ’์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
// ๊ธฐ๋ณธ ์‚ฌ์šฉ
<input {...register("username")} />

// ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™ ์ถ”๊ฐ€
<input {...register("username", { 
  required: true,  // ํ•„์ˆ˜ ์ž…๋ ฅ
  minLength: 2,    // ์ตœ์†Œ 2๊ธ€์ž
  maxLength: 20    // ์ตœ๋Œ€ 20๊ธ€์ž
})} />
  1. ์—๋Ÿฌ ์ฒ˜๋ฆฌ:
<input {...register("username", { required: true })} />
{errors.username && <p>์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!</p>}
  1. ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •:
const { register } = useForm({
  defaultValues: {
    username: "๊น€์ฝ”๋”ฉ",
    email: "kim@coding.com"
  }
});

์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ:

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

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>์ด๋ฉ”์ผ</label>
        <input 
          {...register("email", { 
            required: "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค",
            pattern: {
              value: /\S+@\S+\.\S+/,
              message: "์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค"
            }
          })} 
        />
        {errors.email && <p>{errors.email.message}</p>}
      </div>

      <div>
        <label>๋น„๋ฐ€๋ฒˆํ˜ธ</label>
        <input 
          type="password"
          {...register("password", {
            required: "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค",
            minLength: {
              value: 8,
              message: "8์ž๋ฆฌ ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
            }
          })}
        />
        {errors.password && <p>{errors.password.message}</p>}
      </div>

      <button type="submit">๊ฐ€์ž…ํ•˜๊ธฐ</button>
    </form>
  );
}

์žฅ์ :
1. ๊ฐ„๋‹จํ•œ ํผ ๊ด€๋ฆฌ
2. ์ ์€ ์ฝ”๋“œ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ตฌํ˜„
3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์‰ฌ์›€
4. ๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™”

๊ธฐ์กด ๋ฐฉ์‹๊ณผ์˜ ์ฐจ์ด:

// ๊ธฐ์กด ๋ฐฉ์‹
const [email, setEmail] = useState("");
<input 
  value={email}
  onChange={(e) => setEmail(e.target.value)}
/>

// React Hook Form
<input {...register("email")} />

React Hook Form์„ ์‚ฌ์šฉํ•˜๋ฉด useState๋กœ ๊ฐ๊ฐ์˜ input ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ ธ์„œ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์ง‘๋‹ˆ๋‹ค!

๐Ÿšจ ๋„์ž… ๊ณผ์ •์—์„œ ๋งŒ๋‚œ ๋ฌธ์ œ๋“ค

1. react-router-dom๊ณผ์˜ ๋ฒ„์ „ ์ถฉ๋Œ๋ฌธ์ œ ๋ฐœ์ƒ

yarn add react-hook-form
์„ ์‚ฌ์šฉํ•˜๋‹ˆ ์–ด๋ผ...

yarn add react-hook-form
yarn add v1.22.22
[1/4] ๐Ÿ”  Resolving packages...
[2/4] ๐Ÿšš  Fetching packages...
error react-router-dom@7.1.5: The engine "node" is incompatible with this module. Expected version ">=20.0.0". Got "18.20.5"
error Found incompatible module.
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.

์˜ˆ?? ๋ญ๊ฐ€ ์ž˜๋ชป๋œ๊ฑฐ์ง€..
์ด๋Ÿด๋• ๋ญ๋‹ค? ์„œ์น˜๋‹ค..

์›์ธ์„ ์ฐพ์•„๋ณด๋‹ˆ, react-router-dom๊ณผ์˜ ๋ฒ„์ „์ด ๋งž์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๊ฒƒ..
ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ router๊ธฐ๋Šฅ์— ์ตœ๋Œ€ํ•œ ๋ฌด๋ฆฌ๊ฐ€ ๊ฐ€์ง€ ์•Š๋„๋ก ๋ฒ„์ „์„ ์กฐ์ ˆํ•˜์˜€๋‹ค.

as-is: "react-router-dom": "^7.1.5"
to-be: "react-router-dom": "6.4.0", "react-hook-form": "^7.54.2",

์œ„๊ณผ ๊ฐ™์ด ๋งž์ถ”์–ด ์ฃผ๋‹ˆ, ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™!!

2. ์ž˜๋ชป๋œ submit ํ•ธ๋“ค๋ง

// โŒ ์ฒ˜์Œ ์‹œ๋„ํ•œ ๋ฐฉ์‹
<form onSubmit={(e) => {
  e.preventDefault()
  handleSubmit(handleAddFeed)
}}>

// โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹
<form onSubmit={handleSubmit(handleAddFeed)}>

handleSubmit์ด ์ด๋ฏธ preventDefault๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฑธ ๋ชฐ๋ผ์„œ ๋ฐœ์ƒํ•œ ์‹ค์ˆ˜์˜€์Šต๋‹ˆ๋‹ค.

2. ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ

// โŒ ๋ฌธ์ œ์˜ ์ฝ”๋“œ
<StFormTitleInput
  value={addFeedData.title}
  onChange={(e) => handleInputChange(e, 'title')}
/>

// โœ… ํ•ด๊ฒฐ: register ํ•จ์ˆ˜ ์‚ฌ์šฉ
<StFormTitleInput
  {...register("title", { required: true })}
/>

React Hook Form๊ณผ input์ด ์ œ๋Œ€๋กœ ์—ฐ๋™๋˜์ง€ ์•Š์•„ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.
์‹ค์งˆ์ ์œผ๋กœ ์–ด๋–ค ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ• ๊ฑฐ์•ผ! ๋ผ๊ณ  ๋ช…์‹œํ•˜์ง€ ์•Š์€์…ˆ

๐Ÿ’ก ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ตฌํ˜„

1. ๊ธฐ๋ณธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

{...register("title", {
  required: true,
  minLength: {
    value: 6,
    message: "์ œ๋ชฉ์€ ์ตœ์†Œ 6์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค"
  },
  maxLength: {
    value: 50,
    message: "์ œ๋ชฉ์€ ์ตœ๋Œ€ 50์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"
  },
  setValueAs: (value) => value.trim()
})}

setValueAs๋กœ ๋ฐ”๋กœ ๊ณต๋ฐฑ์ œ๊ฑฐ๋ฅผ ์ง„ํ–‰ํ•ด์ฃผ๊ณ ..

2. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ

{errors?.title && (
  <p>
    {errors.title.type === "required" && "์ œ๋ชฉ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค."}
    {errors.title.type === "minLength" && "์ œ๋ชฉ์€ ์ตœ์†Œ 6์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค"}
    {errors.title.type === "maxLength" && "์ œ๋ชฉ์€ ์ตœ๋Œ€ 50์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"}
  </p>
)}

โœจ ์‹ ๊ธฐํ–ˆ๋˜ ์ ๋“ค

  1. ์ž๋™ ํผ ์ œ์ถœ ๋ฐฉ์ง€

    • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ์ž๋™์œผ๋กœ ์ œ์ถœ ๋ง‰์•„์คŒ
    • ๋ณ„๋„ ์ฒ˜๋ฆฌ ๋ถˆํ•„์š”
  2. ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ ์ œ๊ฑฐ

    // ์ด๋Ÿฐ ์ฝ”๋“œ๋“ค์ด ๋ชจ๋‘ ํ•„์š” ์—†์–ด์ง
    const [addFeedData, setAddFeedData] = useState(INITIAL_ADD_FEED_DATA);
    const handleInputChange = (e, field) => {...};
    useEffect(() => ...์ƒ๋žต ,[])
  3. ๊น”๋”ํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

    • formState: { errors }๋กœ ์‰ฝ๊ฒŒ ์—๋Ÿฌ ์ƒํƒœ ํŒŒ์•…
    • ํƒ€์ž…๋ณ„ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

๐Ÿ’ญ ๋ฐฐ์šด ์ 

  1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์˜ ์ค‘์š”์„ฑ
  2. ๋ฌธ์„œ๋ฅผ ๊ผผ๊ผผํžˆ ์ฝ์—ˆ๋‹ค๋ฉด ํ”ผํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์‹ค์ˆ˜๋“ค์ด ์žˆ์—ˆ์Œ
  3. ๋•Œ๋กœ๋Š” ๋” ์ ์€ ์ฝ”๋“œ๊ฐ€ ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ

React Hook Form์„ ๋„์ž…ํ•˜๋ฉด์„œ ๊ฒช์€ ์‹œํ–‰์ฐฉ์˜ค๋“ค์ด ์˜คํžˆ๋ ค ์ข‹์€ ํ•™์Šต ๊ฒฝํ—˜์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  ์ ‘ํ•˜๋Š”๊ฑด ์ƒˆ์‚ผ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
react-hook-form ๊ณต์‹๋ฌธ์„œ

profile
์ฃผ๋‹ˆ์–ด ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ์žฅ๊ธฐ ๊ธฐ๋ก๊ธฐ๋ก

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