React 폼 요소 제어하기

2023. 11. 16. 07:50UX Engineer 이야기
seonju.lee

들어가며

React form API에서 가장 많이 사용하고 있는 React Hook Form 라이브러리에 대해서 알아보려고 합니다. 프로젝트 진행하면서 해당 라이브러리가 채택된 이유는 React Hook Form이 종속성이 없는 가벼운 라이브러리이고, 재 렌더링 횟수를 최소화하여 성능에 안정적이고 타입스크립트를 기본으로 제공해 작업 환경에 큰 어려움 없이 적용하기 좋아 선정하게 되었습니다.

 

React-hook-form 시작하기

react-hook-form 설치는 자바스크립트 패키지 매니저인 npm을 이용하여 간편하게 설정이 가능합니다.

npm install react-hook-form

useForm Hook 이용하기

우리가 일반적으로 작업해야 하는 Form 요소 요건 사항은 아래와 같습니다.

  1. 입력 필수 요소 검증 (required)
  2. 입력 제한 요소 검증 (pattern, maxLength)
  3. 입력값 유효성 검증 (error 케이스)

Register

1번, 2번에 대한 입력 요소 검증은 useForm hook의 인스턴스 중 register, handleSubmit를 이용하여 폼 요소와 액션이 필요한 버튼 컴포넌트에 연결을 해야 합니다. register 첫 번째 인자에는 key에 해당하는 필드명을, 두 번째 인자로는 입력 요소 옵션을 객체 요소로 추가해 줍니다. 첫 번째 인자의 값에 폼 요소의 데이터가 객체로 반환되므로 고유하게 작성해야 합니다.

  • 입력 요소 옵션 종류는 다음과 같습니다.
    • required(type : boolean)
    • pattern(type : RegExp)
    • min(type : number)
    • max(type : number)
    • maxLength(type : number)
    • validation(type : Function | Object)
    • valueAsNumber(type : boolean)
    • valueAsDate(type : boolean)
// 인풋 요소에 register 등록하기
<label for="firstName">이름</label>
<input
  id="firstName"
  {...register('firstName', {
    required: true,
    pattern: /^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]{1,30}$/,
    minLength: 2,
    maxLength: 7,
  })}
/>
<label for="count">빈도수</label>
<input
  id="count"
  {...register('count', {
    required: true,
    min: 0,
    max: 10,
    validate: {
      enable: (value: number) => value > 0 || '개수를 다시 입력해 주세요.',
      max: (value: number) => value <= 10 || '한사람당 10개 제한있습니다.',
    },
    valueAsNumber: true,
  })}
/>

FormState

register 함수에 연결된 값이 validator 검증의 결과인 에러 정보는 formState의 errors의 데이터를 통해 확인이 가능합니다. 에러가 발생하면 객체에 타입이 같이 명시됩니다. 즉, required, pattern, maxLength, max에 의한 validator 타입인지 보여줍니다. 우리가 일반적으로 form 입력 시 error에 따라 UI에서 안내 문구를 표기하는데 react-hook-form register 인스턴스에 각 value, message로 입력값과 안내 문구를 같이 전달하면, 타입에 맞게 동적으로 변경이 가능합니다.

// register에 errors 발생 시 안내 문구 등록
const {
  formState: { errors },
  setValue,
} = useForm({ defaultValues: { firstName: 'Kim' } });
<input
  {...register('firstName', {
    required: true,
    pattern: {
      value: /^[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]{,30}$/,
      message: '영문, 한글 최대 30자까리 입력해 주세요.',
    },
    minLength: {
      value: 2,
      message: '입력 값이 너무 짧습니다.',
    },
    maxLength: {
      value: 7,
      message: '입력 자리수를 확인해 주세요.',
    },
  })}
  errors={errors}
/>;

FormState에는 errors뿐만 아니라 Form 데이터의 디폴트 값을 설정하는 defaultValues, 기본값에서 수정된 필드 데이터를 담는 dirtyFields, form 데이터를 submit 할 때 변경하는지 설정하는 isSubmitting, 변경될 때마다 변경하는지 설정하는 isLoading 와 같은 요소들이 있습니다.

watch

Form 요소에서 자주 사용되는 UI는 required 속성이 지닌 인풋 요소가 모두 입력을 완료하거나, 입력된 요소가 유효성 검사를 통과한 경우만 submit 버튼이 활성화하거나 특정 조건에 따라서 인풋 요소가 추가되면서 필드를 다르게 노출하는 기능들이 있는데 이럴 때 watch 함수를 활용할 수 있습니다.

const {
  formState: { errors },
  watch,
} = useForm({ mode: 'onChange' });
const { firstName, count, ...watch } = watch(); // register에 등록된 key 값 전체 필드
const id = watch('count'); // 특정 필드
// count 개수에 따라 체크박스 요소 추가
{
  watch('count') > 1 && (
    <div>
      <label for="weekday">평일</label>
      <input id="weekday" type="checkbox" {...register('weekday')} />
      <label for="weekday">주말</label>
      <input id="weekend" type="checkbox" {...register('weekend')} />
    </div>
  );
}

handleSubmit & reset

인풋 요소들이 유효성 검사를 통과해서 등록 버튼으로 submit 이벤트가 실행될 때, 데이터를 최종으로 검증이 필요할 때 handleSubmit 함수를 이용해서 register에 등록된 필드 확인이 가능합니다. 그리고 submit 이벤트 발생 후 해당 페이지를 새로 고침을 하지 않은 이상 Form 요소 내 인풋 요소들의 값들을 직전 값들을 저장해서 유지하기 때문에 값을 초기화하는 reset 함수가 필요합니다.

const {
  reset
} = useForm({mode: "onChange" });

 const onSubmit = handleSubmit(data: any) => {
  api.post(url, data).then( (res) =>  {
    reset();
  )
});

Controller, useController

Controller 컴포넌트는 데이터값을 따로 제어가 필요할 경우, form 요소를 랩핑해서 필요한 field에 onChange, value를 구조 분해하여 값을 이용할 수 있고 useController는 useForm과 Controller가 통합된 형태로 직접 form 요소를 제어하는 hook입니다.

// Controller 예시
const {
  control,
  handleSubmit,
  formState: { errors },
} = useForm();

<Controller
  id="phone"
  name="phone"
  control={control}
  render={({ field: { onChange, value } }) => (
    <input
      {...field}
      type="text"
      placeholder="Enter your phone Number"
      value={value}
      onChange={(e) => {
        const plainPhoneNumber = e.target.value.replaceAll('-', '').replace(/\D/g, '');
        const result = plainPhoneNumber
          .replace(/(\d{2,3})(\d{3,4})(\d{4})/, '$1-$2-$3')
          .replace(/(-\d{4})\d+/, '$1');
        onChange(result);
      }}
    />
  )}
/>;

// useController 예시
const {
  field: { ref, value, onChange },
  fieldState: { invalid, error },
} = useController({
  name,
  control,
  rules,
  defaultValue: '',
});

<input ref={ref} value={value} onChange={onChange} />;
{
  invalid && <span role="alert">{error?.message ?? 'This field is invalid'}</span>;
}

 

 마치며

react 환경에서 Form 요소를 쉽게 제어하는 라이브러리에 대해 알아봤는데요, 이 글을 읽으시는 분들이 프로젝트에서 Form 요소 제어 사용 및 이해하는 데 조금이나마 도움이 되셨으면 좋겠습니다.

부족한 부분, 잘못된 내용이 있다면 댓글로 알려 주시면 수정하겠습니다.

읽어주셔서 감사합니다.

이 글은 pxd XE Group Blog에서도 보실 수 있습니다.

참고문서