
ํ๋ก์ ํธ๋ฅผ ์์ํ๊ธฐ ์ ๋ถํฐ ๋ค์ ํ๋ก์ ํธ์์๋ ๊ผญ ์ฌ์ฉํด๋ณด๊ณ ์ถ์๋ ์คํ ์ค ํ๋๊ฐ React Hook Form์ด์๋ค. ์ด์ ํ๋ก์ ํธ์์๋ ์น ํ์ค ๋ฑ์ ์ ํ ์๊ฐํ์ง ์๊ณ ๊ตฌํ ํ๋๋ง์ ๋ชฉํ๋ก ๋์๋๋ฐ ๊ทธ ์ค์์ ์ ์ผ ๊ตฌํ๋ง ์๊ฐํ๋ค๋ผ๊ณ ๋๊ปด์ง ๋ฒํ ๋ถ๋ถ์ด form๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ด์๋ค. form ํ๊ทธ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ input/button ๋ง์ ์ฌ์ฉํด์ form์ฒ๋ผ ๋์ํ๊ฒ ํ๋๋ฐ, ์ดํ form ํ๊ทธ์ ๋ํด์ ๊ณต๋ถ๋ฅผ ํด๋ณด๋ ์๋ชป ๊ตฌํํ๋ค๋ผ๋ ์๊ฐ์ด ๋ค์๋ค. ๊ทธ๋ฆฌ๊ณ form์ input๋ค์ด ๋ง์์ง๋ฉด์ useState ๋ณ์๋ค์ ์ ๋ง ๋ง์ด ์ ์ธํ๊ฒ ๋๊ณ ๊ด๋ฆฌ๋ ๋ถํธํ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ๊ทธ๋ฌ๋ ์ค ์๊ฒ ๋ ๊ฒ React Hook Form ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก form ๊ด๋ฆฌ์ ์ข๋ค๋ ๊ฒ์ด์๋ค. ๊ฑฐ๊ธฐ๋ค๊ฐ React Hook Form์ ๋ง์ด๋ค ์ฌ์ฉํด ๋ฐฐ์๋๋ฉด ์ ์ฉํ๊ฒ ๋ค ์ถ์๋ค.
โป ์ ๋ฆฌ๋ฅผ ์ํด ํ๋ก์ ํธ์์ ๊ตฌํํ ์ฝ๋๋ค์ ํ๋จ์์ ์ ๋ฆฌํ๊ณ React Hook Form ์ฌ์ฉ๋ฒ์ ์ํ ๊ฐ๋จํ ์ฝ๋๋ค์ ์ค๋ช ์ ์ํ ์ต์ํ์ ์ฝ๋๋ก ์ค๋ช ํ๋ค.
์๋ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํด์ค๋ค.
npm install react-hook-form
๋ค์๊ณผ ๊ฐ์ด useForm ํ
์ ํตํด ํ์ํ ํจ์๋ค์ ๋ถ๋ฌ์ ์ฌ์ฉํด์ฃผ๋ฉด ๋๋ค. ์๋๋ ๊ฐ๋จํ ์์์ด๋ค. ์ฃผ์ ํจ์๋ค์ ๋ํด์๋ ์๋์์ ์์ธํ๊ฒ ์ค๋ช
ํ๋ค.
const { register, handleSubmit, formState: { errors } } = useForm();
ํผ ํ๋๋ฅผ 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์ ๋์ผํด์ผ ํ๋ค๋ ์ ํจ์ฑ ์กฐ๊ฑด์ด ์ค์ ๋ ๊ฒ์ด๋ค.
ํผ์ด ์ ์ถ๋์์ ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์คํํ๊ณ , ์ ํจํ๋ฉด ์ ์ถ ํจ์(์ฝ๋ฐฑ ํจ์)๋ฅผ ํธ์ถํ๋ค.
<form onSubmit={handleSubmit(onSubmit)}>
{/* ํผ ๋ด์ฉ */}
</form>
์ฒซ ๋ฒ์งธ ์ธ์ ๋ง๊ณ ๋ ๋ ๋ฒ์งธ ์ธ์๋ฅผ ๊ฐ์ง๋๋ฐ ์ด๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ์ง ๋ชปํ์ ๋ ์คํ๋ ์ฝ๋ฐฑ ํจ์์ด๋ค.
handleSubmit(onSubmit, onError);
ํผ์ ์ ์ฒด ์ํ๋ฅผ ์ถ์ ํ๋ค. ํนํ ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ์์ฃผ ์ฌ์ฉ๋๋ค.
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({ name: "John" }); // ํน์ ํ๋์ ์ด๊ธฐ๊ฐ์ ์ฃผ๊ณ ๋ฆฌ์
ํน์ ํ๋์ ๊ฐ์ ์๋์ผ๋ก ์ค์ ํ๋ค.
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 });
ํผ์ ํ์ฌ ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
const values = getValues(); // ํผ์ ๋ชจ๋ ํ๋ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
const nameValue = getValues("name"); // ํน์ ํ๋ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
ํน์ ํ๋๋ ์ ์ฒด ํผ ๊ฐ์ ๋ณ๊ฒฝ์ ์ค์๊ฐ์ผ๋ก ๊ฐ์ํ๋ค.
const name = watch("name"); // "name" ํ๋์ ๊ฐ์ ์ค์๊ฐ์ผ๋ก ๊ฐ์
const allValues = watch(); // ๋ชจ๋ ํ๋ ๊ฐ ๊ฐ์
์๋์ผ๋ก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค. ํน์ ํ๋๋ ์ ์ฒด ํ๋๋ฅผ ๊ฒ์ฆํ ์ ์๋ค. ํน์ ์ด๋ฒคํธ ๋ฐ์ ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์๋์ผ๋ก ์คํํ ๋ ์ฌ์ฉ๋๋ค.
trigger(); // ์ ์ฒด ํ๋์ ๋ํด ์ ํจ์ฑ ๊ฒ์ฌ
trigger("name"); // ํน์ ํ๋์ ๋ํด ์ ํจ์ฑ ๊ฒ์ฌ
useForm์ ์ต์ ์ ์ ๋ฌํด ํผ์ ์ ํจ์ฑ ๊ฒ์ฌ์ ํผ ๋์ ๋ฐฉ์์ ์ ์ดํ ์ ์๋ค.
useForm({
mode: 'onSubmit',
criteriaMode: 'all',
reValidateMode: 'onChange',
shouldFocusError: true
});
ํผ์ ๊ฐ ํ๋์ ๋ํ ์ด๊ธฐ๊ฐ์ ์ค์ ํ๋ค. ๋น๋๊ธฐ ํจ์๋ก๋ ์ค์ ๊ฐ๋ฅํ๋ค.
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,
};
}
});
์ธ๋ถ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ฒ๋ฆฌํ๋ค.
const schema = yup.object().shape({
name: yup.string().required(),
age: yup.number().positive().integer().required(),
});
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
ํผ์ ์ ํจ์ฑ ๊ฒ์ฌ์ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ์ ์ญ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ๋ ์ฌ์ฉ๋๋ค. ์ฃผ๋ก resolver์ ํจ๊ป ์ฌ์ฉ๋๋ค.
const { register } = useForm({
context: { isLoggedIn: true }
});
์ ํจ์ฑ ๊ฒ์ฌ ์คํจ ์ ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ํ๊ธฐ ์ ๊น์ง ์ง์ฐ ์๊ฐ์ ์ค์ ํ๋ค. ํ๋ ๊ฐ์ด ๋ณ๋๋๋ ์ค๊ฐ์๋ ์๋ฌ ๋ฉ์์ง๊ฐ ๋ฐ๋ก ํ์๋์ง ์๋๋ก ํ ์ ์๋ค.
useForm({
delayError: 500 // ์๋ฌ ๋ฉ์์ง๋ฅผ 500ms ํ์ ํ์
});
HTML5์ ๊ธฐ๋ณธ ์ ํจ์ฑ ๊ฒ์ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ง ์ฌ๋ถ
useForm({
shouldUseNativeValidation: true
});
ํ๋ ์ ํจ์ฑ ๊ฒ์ฌ์์ ๋ฐ์ํ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ฒซ ๋ฒ์งธ ์๋ฌ๋ง ๋ฐํํ ์ง, ๋ชจ๋ ์๋ฌ๋ฅผ ๋ฐํํ ์ง ๊ฒฐ์ ํ๋ค. ('firstError' (default) | 'all')
์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์ธ์ ์คํ๋ ์ง๋ฅผ ์ค์ ํ๋ค. ('onSubmit' (default) | 'onBlur' | 'onChange' | 'onTouched' | 'all')
onSubmit: ํผ ์ ์ถ ์์๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
-> ์ฌ์ฉ์ ์ ๋ ฅ์ด ๋ฐ๋ ๋๋ง๋ค ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํ์ง ์์ผ๋ฏ๋ก ์ฑ๋ฅ์ ์ ๋ฆฌ
onBlur: ํผ ํ๋์์ ํฌ์ปค์ค๋ฅผ ์์ ๋๋ง๋ค ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ (์ฌ์ฉ์๊ฐ ์ ๋ ฅ ํ๋๋ฅผ ํด๋ฆญํ ํ ๋ค๋ฅธ ํ๋๋ก ์ด๋ํ ๋๋ง๋ค ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํ)
-> ํ๋์ ์ค๋ฅ๋ฅผ ์ฆ์ ์ฌ์ฉ์์๊ฒ ์๋ฆด ์ ์์
onChange: ํผ ํ๋์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
-> ๋ง์ ์ ๋ ฅ ๋ณํ๊ฐ ๋ฐ์ํ ๋ ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค.
onTouched: ํผ ํ๋๊ฐ ์ฌ์ฉ์๊ฐ ์ํธ์์ฉํ ํ์๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
-> ์ฌ์ฉ์ ๊ฒฝํ์ ๊ณ ๋ คํ์ฌ, ์ ๋ ฅ์ด ์๋ฃ๋ ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ์ฌ ํผ๋๋ฐฑ์ ์ ๊ณต
all: ๋ชจ๋ ์ํ์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ
์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํจํ ํ ์ธ์ ๋ค์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์คํํ ์ง ๊ฒฐ์ ํ๋ค. ('onChange' (default) | 'onBlur')
์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํจํ ํ๋๋ก ์๋ ํฌ์ปค์ค๋ฅผ ์ด๋ํ ์ง ์ฌ๋ถ
ํผ์์ ํ๋๊ฐ ์ธ๋ง์ดํธ๋ ๋ ํด๋น ํ๋์ ๊ฐ์ ํผ ์ํ์์ ์ ๊ฑฐํ ์ง ์ฌ๋ถ
ํผ ๋ฐ์ดํฐ๊ฐ ์ด๊ธฐํ๋ ๋ ํ๋์ dirty ์ํ๋ฅผ ์ ์งํ ์ง๋ฅผ ๊ฒฐ์ ํ๋ค.
useForm({
defaultValues: { name: "John" },
keepDirtyOnReinitialize: true
});
ํผ ํ๋๊ฐ ์ธ๋ง์ดํธ๋ ํ์๋ ๊ฐ์ ์ ์งํ ์ง ์ฌ๋ถ
๋๋ถ๋ถ์ ์ฝ๋๊ฐ ๋น์ทํ๊ธฐ ๋๋ฌธ์ '๋๋ค์ 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 ์ฒ๋ฆฌ๋ผ๋์ง ์ค์๊ฐ ์ค๋ฅ ํ์๋ฅผ ์ฌ์ดํธ์์ ์ ๋ง ๋ง์ดํ๋ ๊ฒ ๊ฐ์๋ฐ ๊ตฌํํ๊ธฐ๊ฐ ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค. ๊ทธ๋๋ ๊ตฌํ ๋์ด๋๋ ์ด๋ ต์ง ์๊ณ , ๊ด๋ฆฌ๋ ํธํ๊ธฐ ๋๋ฌธ์ ๋ค์์๋ ์ฌ์ฉํ ๋ฒํ ๋๋? ์ผ๋จ ๋ง์กฑ!