패스트캠퍼스 데브캠프

토이 프로젝트 3 - zod와 react-hook-form 사용하여 유효성 검사

vitamin3000 2025. 1. 12. 17:37

 

이번 프로젝트에서, 아래와 같이 
카테고리 명, 카테고리 썸네일, 카테고리에 넣을 영상 입력값에 대한 유효성 검사를 수행하기 위해

zod와 react-hook-form을 사용하려 한다.

 

우선, 아래의 명령어로 라이브러리를 설치한다.

npm install react-hook-form @hookform/resolvers zod

 

필요한 라이브러리를 import 한다

import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

 

입력 폼에 대한 어떤 값에 검사하는 조건과 메시지를 설정하는 스키마를 작성한다.

이때 기존에 작성하던 파일은 tsx파일로, Page를 담당하고 있었다.

따라서 단일 책임 원칙에 따라 

src/schema/categorySchema.ts 파일을 생성하여 선언하고, import하도록 하였다.

import { z } from 'zod';

const categorySchema = z.object({
  categoryName: z.string().min(1, { message: '카테고리 명을 입력해주세요' }),
  imgFile: z
    .string()
    .min(1, { message: '썸네일을 선택해주세요' })
    .refine(
      value => {
        const validPrefixes = [
          'data:image/jpg;base64,',
          'data:image/jpeg;base64,',
          'data:image/png;base64,',
        ];

        return validPrefixes.some(prefix => value.startsWith(prefix));
      },
      {
        message: 'jpg, jpeg, png 파일만 허용됩니다.',
      },
    ),
  videos: z.string().min(1, { message: '최소 하나의 동영상을 선택해주세요' }),
});

export { categorySchema };
  5 changes: 4 additions & 1 deletion5  
tsconfig.json

 

다음은 useForm을 사용하여 폼을 관리한다.

type CheckInput = {
  categoryName: string;
  imgFile: string;
  videos: string;
};
  
const { control, formState: { errors }, handleSubmit, setValue, clearErrors } = useForm<CheckInput>({
    resolver: zodResolver(schema),
    defaultValues: {
      categoryName: '',
      imgFile: '',
      videos: '',
    }
  });

 

여기서 중요 포인트는 다음과 같다.
1. contorl : 폼의 상태와 입력 요소를 연결하는데 사용한다.

2. setValue : 특정 입력 필드의 값을 설정하는데 사용

3. resolver : zodResolver는 스키마 기반의 유효성 검사를 실행

 

작성한 스키마를 바탕으로 react-hook-form을 사용하여 입력 폼을 작성한다.

<form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          name="categoryName"
          control={control}
          render={({ field }) => (
            <LabelInput
              title="카테고리 명"
              placeholder="카테고리 명을 입력해주세요."
              onChange={(e) => {
                field.onChange(e);
                if (e.target.value) {
                  clearErrors('categoryName');
                }
              }}
              value={field.value}
            />
          )}
        />
        {errors.categoryName && (
          <p className="text-sm text-red-500">{errors.categoryName.message}</p>
        )}

 

여기서 위에서 설정한 입력값을 체크한다. {errors.categoryName}으로.. 

 

여기서, 다른 곳과 다른 점은 Contorller를 사용한 것이다.
그 이유는 text를 입력받는데, onChange와의 충돌로 값이 입력되었음에도 에러메시지가 사라지지 않기 때문이다.

  1. Controller: 커스텀 입력 컴포넌트를 사용할 수 있게 함
  2. name:  폼 상태에서 관리할 필드의 이름을 지정, 여기서는 "categoryName"이라는 이름을 가진 필드를 관리
  3. control: 폼 상태와 입력 필드를 연결
  4. render: Controller가 렌더링할 컴포넌트를 정의 여기서 field 객체가 파라미터로 전달되며, 이 객체에는 입력 필드를 제어하는 데 필요한 여러 메서드와 프로퍼티가 포함되어 있다.
  5. LabelInput: 이 컴포넌트는 title과 placeholder 속성을 통해 사용자에게 입력할 내용을 안내한다
  6. onChange: 사용자가 입력 필드의 값을 변경할 때 호출되는 함수이다. 여기서는 field.onChange(e)를 호출하여 React Hook Form에 입력값을 전달. 입력값이 존재할 경우 clearErrors('categoryName')를 호출하여 해당 필드의 오류 메시지를 지운다.
  7. value: field.value를 사용하여 현재 입력 필드의 값을 설정하는데 이는 Controller가 관리하는 폼 상태와 동기화된다.

결과화면