FRONT/React

[REACT] react-hook-form 사용하기 - 구현(2)

eunoia07 2022. 6. 28. 17:54

1편(개념편)에서 이어지는 내용입니다.😎

위 사진처럼 회원가입, 로그인 등 폼을 활용해 사용자에게 입력 필드를 제공할 때 입력과 동시에 유효성 검사를 진행하면 사용자 친화적으로 접근할 수 있어 많은 사이트들이 해당 유효성 검사 방식을 사용합니다.

react-hook-form을 사용해 입력과 동시에 유효성 검증을 구현해보겠습니다.

1. react-hook-form의 기본 설정값


1편에서 설명했듯이 react-hook-form은 submit 함수가 호출 될 때, 즉 사용자가 어떤 행위를 해야 유효성 검증을 진행합니다. 이 부분을 해결하기 위해 입력 필드에 validate 옵션을 통해 함수로 유효성 검사하는 부분을 분리하겠습니다.

2. 구현


[a.기능 설명]

일단 입력 필드의 상태를 세 가지의 타입으로 나눴습니다.

- default : 기본 상태

- error : 유효성 검사 실패 상태

- complete : 유효성 검사 성공 후 입력 완료 상태

[b.유효성 검사 구현]

const Form = () => { 
    const { register, handleSubmit, formState: { errors } } = useForm();

    const validateNickname = (value) => {
      if (value.length <= 2) {
        return '2글자 이상 입력해주세요.';
      }
      return true;
    };

    return (
        <form onSubmit={handleSubmit(handleRegistration)}>
            <input
              type="text"
              name="nickname"
              ref={register('nickname', {
                validate: validateNickname
              })}
            />
            {errors?.nickname && errors.nickname.message}
         <button>Submit</button>
        </form>
    )
}

react-hook-form에서 제공하는 validate 옵션을 이용해 입력 필드의 값을 받아 유효성을 검증하는 함수를 등록합니다.

errors?.nickname를 통해서 react-hook-form에 등록된 nickname이 errors에 있으면 message를 출력합니다.

[c. 구현]

이제 validate에 등록한 함수에서 반환한 값을 가지고 입력 필드의 스타일을 변경해줍니다.

import styled from "@emotion/styled";
import { useForm } from "react-hook-form";

const Form = () => { 
    const { register, handleSubmit, formState: { errors } } = useForm();

    const validateNickname = (value) => {
      if (value.length <= 2) {
        return '2글자 이상 입력해주세요.';
      }
      return true;
    };

    return (
        <form onSubmit={handleSubmit(handleRegistration)}>
            <Wrapper error={errors?.nickname && true} >
                <Input
                  type="text"
                  name="nickname"
                  ref={register('nickname', {
                    validate: validateNickname
                  })}
                />
            {errors?.nickname && errors.nickname.message}
            </Wrapper>

         <button>Submit</button>
        </form>
    )
}

const Wrapper = styled.div<{error?:boolean}>`
  display: flex;
  border-bottom: ${props => props.error ? `1px solid red` : `1px solid black` };
  color : ${props => props.error ? `red` : `black` };
`;

const Input = styled.input`
  height: 40px;
  border: none;
  outline: none;
`;

이렇게, 사용자가 입력과 동시에 유효성을 확인하는 기능을 react-hook-form을 통해 구현했습니다.

3. 리팩토링


🤔 한 개의 입력 필드에 한 개의 함수가 생성되는 코드가 반복되고 있어, 입력 필드를 컴포넌트로 분리시켜 재사용 할 수 있도록 리팩토링을 진행했습니다.

- Input.tsx
- Form.tsx

Form과 Input으로 컴포넌트를 분리하고 Form에 Input을 등록시키는 방식으로 개발합니다.

[a. 구조 변경 - Form.tsx]

Form과 Input으로 컴포넌트를 분리하고 Form에 Input을 등록시키는 방식으로 개발합니다.

/*Form.tsx*/
import { useForm, useWatch, FormProvider, FieldValues } from "react-hook-form";

interface IFormInputs extends FieldValues {
  nickname:string;
}

const Form = () => {
 const methods = useForm<IFormInputs>({
    defaultValues:{
      nickname : ""
    },
    mode:"onChange"
  });

  const {
    handleSubmit
  } = methods;

  return(
   <FormProvider {...methods} >
        <form onSubmit={handleSubmit(onSubmit)}>
       <Label>E-mail</Label>
            <Input
              name="nickname"
              rules={{
                required:true,
                minLength:{
                  value:2,
                  message:'2글자 이상 입력해주세요.'
                },
              }}
              type="text"
              placeholder="닉네임을 입력해주세요."
              />
          </form>
      </FormProvider>
    )
}

 

useForm({mode : "onChange"})
  • mode 옵션은 onSubmit 이벤트가 발생하기 전 유효성 검사를 언제 할건지를 결정할 수 있습니다. onChange로 설정해 입력 필드가 변경될 때 유효성 검사가 실행되도록 설정합니다.

FormProvider

  • react-hook-form에서 제공하는 FormProvider를 사용하면 각 input에 수동으로 props를 전달할 필요 없이 Input이 Form에 등록됩니다.

[b. 구조 변경 - Input.tsx]

/* Input.tsx */
import { useFormContext, useController, FieldPath, FieldValues, UseControllerProps } from "react-hook-form";

interface InputProps<
    TFieldValues extends FieldValues = FieldValues,
    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> 
extends UseControllerProps<TFieldValues, TName>{
   type?:string;
   placeholder?:string;
}


function Input<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>
(props:InputProps<TFieldValues, TName>) {
  const name = props.name;
  const {field, fieldState} = useController({
    name,
    rules: props.rules
  });

  return (
    <Wrapper error={fieldState.error && true}>
       <Input
         {...field}
          type={props.type}
          placeholder={props.placeholder}
        />
        {fieldState.error && fieldState.error.message}
    </Wrapper>
  );
};

export default Input;

- const {field, fieldState} = useController();

  • useController hook을 사용해 react-hook-form에 등록되어 있는 입력필드의 상태(fieldState)와 제어되고 있는 입력필드의 현재 값(field)를 가져옵니다.

- field

  • onChange, name, ref를 포함하고 있는 개체로 입력 필드에 등록해 줍니다.

- fieldState.error

  • 현재 상태를 가져오는 fieldState를 이용해 error 값을 받아오고 error의 값이 있으면 error가 가지고 있는 message 값을 가져올 수 있습니다.