소개
사용할 라이브러리
- react-hook-form
- zod : Type Validation 도구
bashnpm install zod react-hook-form @hookform/resolvers
Zod
validation 기능을 가장 강력하게 사용할 수 있는 라이브러리
React Hook Form과 같이 쓰기에도 좋다
기본 사용 방법
tsximport * as z from "zod"; // validation 변수 생성. 해당 형태로 온전한지를 파악해준다. const isObject = z.object(); // 객체 형태 const isArray = z.array(z.any()); // 배열 형태 const isString = z.number(); // 문자열 형태 const isNumber = z.number(); // 숫자 형태 // validate isObject.parse(data); // 실패시 Exception 발생 isObject.safeParse(data).success; // 실패시 결과물로 나옴
활용 코드 (예시)
tsxconst validSchema = { isEmpty: z.string().length(0).nullable().optional(), // 문자열 length 0개 이상, null, undefined 가능 isBlank: z.string().trim().length(0).nullable().optional(), // 문자열 trim 값 length 0개 이상, null, undefined 가능 isString: z.string(), // 문자열 체크 isNumber: z.number(), // 숫자 체크 isArray: z.array(z.any()), // 배열인지 체크 isEmail: z.email(), // 이메일인지 체크 isPassword: z .string() .min(8) .regex(/[A-Z]/, "Need uppercase") .regex(/[a-z]/, "Need lowercase") .regex(/[0-9]/, "Need number") .regex(/[!@#$%^&*]/, "Need special"), };
React Hook Form
기본 사용 방법
register: input태그에{...register()}를 추가하여 react-hook-form에 등록handleSubmit: submit 로직을 등록getValues(name?): 현재 form에 저장된 필드 값을 가져옴. name 없으면 리스트로 출력setValue(name, value): input onChange 이외에 form에 데이터 저장할 때 사용formState: 현재 form 상태를 보여줌 (errors, isSubmitting, submitCount 등등)zodResolver: zod로 지정한 schema를 Form데이터에 타입으로 지정해줌
jsximport { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; const schema = z.object({ username: z.string().min(2, { message: "이름은 2글자 이상이어야 합니다." }), email: z.string().email({ message: "올바른 이메일 형식이 아닙니다." }), password: z.string().min(8, { message: "비밀번호는 최소 8자 이상이어야 합니다." }) .regex(/[A-Z]/, "Need uppercase") .regex(/[a-z]/, "Need lowercase") .regex(/[0-9]/, "Need number") .regex(/[!@#$%^&*]/, "Need special"), phone: z.string().regex(/^010-\d{4}-\d{4}$/, { message: "010-0000-0000 형식으로 입력해주세요." }), }); type FormData = z.infer<typeof schema>; // typesciprt의 경우 type 확인을 위해 해당 코드 추가 export default function HookFormExample() { const { register, handleSubmit, getValues, setValue, formState: { errors, isSubmitting, submitCount } } = useForm<FormData>({ resolver: zodResolver(schema) }); const onSubmit = (data: FormData) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)} className="p-8 md:p-12 space-y-8"> <input type="text" {...reigster("username")}/> { submitCount > 0 && errors?.username && <>username error</> } <button type="submit" disabled={isSubmitting}>버튼</button> </form> ); }
Controller 사용 방법
Controllercontrol: reigster와 유사한걸로 이해하고 있음.render: field 변수를 가져와서 원하는 방식으로 커스터마이징 가능. 실제 값 저장은 field.value로 들어가므로, 해당 값에 맞게 value 값 지정으로 보여주는 방법도 가능하다- value을 바꾼 상태에서 onChange가 작동되면 현재 value 값으로 e.target.value가 잡히게 되어, 별도 처리가 필요할 수 있다.
jsximport { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; const schema = z.object({ username: z.string().min(2, { message: "이름은 2글자 이상이어야 합니다." }), email: z.string().email({ message: "올바른 이메일 형식이 아닙니다." }), password: z.string().min(8, { message: "비밀번호는 최소 8자 이상이어야 합니다." }) .regex(/[A-Z]/, "Need uppercase") .regex(/[a-z]/, "Need lowercase") .regex(/[0-9]/, "Need number") .regex(/[!@#$%^&*]/, "Need special"), phone: z.string().regex(/^010-\d{4}-\d{4}$/, { message: "010-0000-0000 형식으로 입력해주세요." }), }); type FormData = z.infer<typeof schema>; // typesciprt의 경우 type 확인을 위해 해당 코드 추가 export default function HookFormExample() { const { control, handleSubmit, getValues, setValue, formState: { errors, isSubmitting }, } = useForm<FormData>({ resolver: zodResolver(schema), }); const onSubmit = (data: FormData) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)} className="p-8 md:p-12 space-y-8"> <Controller name="username" control={control} render={({ field }: any) => ( <input type="text" value={`${field.value}님`} onChange={(e) => { // onChange 미지정시 자동으로 field.onChange 진행 // 지정할 경우 override const data = e.target.value.replace("님", ""); field.onChange(data); }} /> )} /> <button type="submit" disabled={isSubmitting}> 버튼 </button> </form> ); }
- 부분 비밀번호 마스킹을 할 때
tsxconst FormMaskedField = ({ label, control, name, error, placeholder, required = false, onFocus = (value: any) => {}, onBlur = (value: any) => {}, }: any) => { const [isFocused, setIsFocused] = useState(true); return ( <Controller name={name} control={control} render={({ field }) => ( <div className="grid grid-cols-1 md:grid-cols-4 gap-2 md:gap-4 items-start border-b border-gray-50 pb-6 last:border-0"> <label className="text-sm font-bold text-gray-700 mt-3 md:col-span-1"> {label} {required && <span className="text-indigo-600">*</span>} </label> <div className="md:col-span-3 space-y-1"> <input {...field} className={`w-full px-5 py-3.5 bg-gray-50/50 border rounded-xl text-black text-sm focus:outline-none focus:ring-2 transition-all placeholder:text-gray-300 border-gray-100 focus:ring-indigo-100 focus:bg-white`} value={isFocused ? onFocus(field.value || "") : onBlur(field.value || "")} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} onChange={(e) => { const raw = e.target.value.replace(/\D/g, "").slice(0, 16); field.onChange(raw); }} placeholder={placeholder} /> {error && ( <p className="text-[11px] text-red-500 font-medium ml-1 flex items-center gap-1"> <span className="w-1 h-1 bg-red-500 rounded-full" /> {error.message} </p> )} </div> </div> )} /> ); };