bkdragon's log

[React-hook-form] register, handleSubmit 살짝 파헤치기 본문

React

[React-hook-form] register, handleSubmit 살짝 파헤치기

bkdragon 2023. 6. 12. 12:36

React-hook-form은 form을 통해 제출할 내용의 유효성 검사 과정을 간편하게 해주고 불필요한 렌더링도 줄여주는 라이브러리이다. 사용 예시를 살펴보고 주요 역할을 하는 register와 handleSubmit에 대해 살펴보려고 한다.

React-hook-form 기본 사용법 예시

import React from 'react';

import { useForm } from "react-hook-form";

interface FormData {
    writer : string;
    title : string;
    contents : string;
}

const ReactHookFromPage = () => {
    const { register, handleSubmit, watch, formState: { errors } } = useForm<FormData>();

    const onSubmit = (data : FormData) => {
        console.log(data)
    }

    return (
        <div>
            <form onSubmit={handleSubmit(onSubmit)}>
                작성자 : <input type='text' {...register('writer')} />
                제목 : <input type='text' {...register('title')}/>
                내용 : <input type='text' {...register('contents')}/>
                <button>등록</button>
                {/* 버튼의 기본 값은 submit, 클릭시 자동으로 form의 onSubmit이 실행됨.*/}
            </form>

        </div>
    );
};

export default ReactHookFromPage;

React-hook-form 없이는 writer에 대한 state, title state, contents state도 다 만들어야 했을 것이다. 코드의 양이 확 줄어들었다.

 

register

입력 또는 선택 요소를 등록하고 유효성 검사 규칙을 React-hook-form에 적용할 수 있다. 사용자의 커스텀 유효성 검사도 허용한다.

const { onChange, onBlur, name, ref } = register('firstName'); 
        
<input 
  onChange={onChange} // assign onChange event 
  onBlur={onBlur} // assign onBlur event
  name={name} // assign name prop
  ref={ref} // assign ref prop
/>

// 위처럼 쓸 수 있지만  아래와 같이 쓰는 편이 편하다. register가 onChange와 같은 것들을 
// 가지는 객체를 리턴하고 이것을 input 태그에 뿌려주는것으로 사용이 완료된다.
<input {...register('firstName')} />

 

좀 더 들어가서 타입을 확인해보면 다음과 같다. (register의 타입)

타입을 확인할 때 세세하게 살펴보는 것도 좋지만 간단하게 인자와 리턴 타입만 봐도 어느정도의 활용법을 이해하는데에는 문제 없다.

// register의 타입
export type UseFormRegister<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(name: TFieldName, options?: RegisterOptions<TFieldValues, TFieldName>) => UseFormRegisterReturn<TFieldName>;

// register의 리턴 타입
export type UseFormRegisterReturn<TFieldName extends InternalFieldName = InternalFieldName> = {
    onChange: ChangeHandler;
    onBlur: ChangeHandler;
    ref: RefCallBack;
    name: TFieldName;
    min?: string | number;
    max?: string | number;
    maxLength?: number;
    minLength?: number;
    pattern?: string;
    required?: boolean;
    disabled?: boolean;
};

필드의 이름을 첫번째 인자로 받고 옵션들을 두번째 인자로 받는다. 이때 받은 필드의 이름을 활용해 onChage, onBlur, ref의 것들을 반환한다.

ref를 반환하는 점에서 비제어 컴포넌트의 방식을 사용하는 것을 알 수 있다. 이로 인해 값이 변할 때 리렌더링이 발생하지 않아 성능적인 이점을 챙길 수 있다. onChange가 있는 것은 실시간으로 값을 추척하는 watch를 사용하기 위함이라고 생각할 수 있다.

 

옵션의 타입도 살펴보자. (복잡한데 쫄지말자 그냥 객체인데 이것저것 옵셔널(Partial)로 들어가있는거다)

xport type RegisterOptions<TFieldValues extends FieldValues = FieldValues, TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = Partial<{
    required: Message | ValidationRule<boolean>;
    min: ValidationRule<number | string>;
    max: ValidationRule<number | string>;
    maxLength: ValidationRule<number>;
    minLength: ValidationRule<number>;
    validate: Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues> | Record<string, Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>>;
    value: FieldPathValue<TFieldValues, TFieldName>;
    setValueAs: (value: any) => any;
    shouldUnregister?: boolean;
    onChange?: (event: any) => void;
    onBlur?: (event: any) => void;
    disabled: boolean;
    deps: InternalFieldName | InternalFieldName[];
}> & ({
    pattern?: ValidationRule<RegExp>;
    valueAsNumber?: false;
    valueAsDate?: false;
} | {
    pattern?: undefined;
    valueAsNumber?: false;
    valueAsDate?: true;
} | {
    pattern?: undefined;
    valueAsNumber?: true;
    valueAsDate?: false;
});

다음과 같은 값들을 옵션에 추가할 수 있다. 이름만으로 유추가 가능하다. 최소 길이나 최대 길이 같은 것들을 설정할 수 있다.

 

ValidationRule 타입까지 살펴보면 이해에 더 도움을 줄 것 같다.

export type ValidationValue = boolean | number | string | RegExp;
export type ValidationRule<TValidationValue extends ValidationValue = ValidationValue> = TValidationValue | ValidationValueMessage<TValidationValue>;
export type ValidationValueMessage<TValidationValue extends ValidationValue = ValidationValue> = {
    value: TValidationValue;
    message: Message;
};

boolean | number | string | RegExp 이런 값들을 받아서 유효성 검사를 하고 메시지를 보여주는구나 정도로 이해하면 되겠다.

 

비밀번호에 minLength를 적용한다 하면 number를 value로 받고 유효성 검사 실패시 넣을 메시지를 작성해서 주면 되겠다.

<form onSubmit={handleSubmit(onSubmit)}>
    작성자 : <input type='text' {...register('writer', {required : {message : 'this is requred', value : true}})} />
    제목 : <input type='text' {...register('title')}/>
    내용 : <input type='text' {...register('contents')}/>
    비밀번호 : <input type='password' {...register('password', {minLength : {value : 8, message : '비밀번호는 최소 8자리여야 합니다.'}})}/>
    <button>등록</button>              
</form>

 

handleSubmit

유효성 검사를 성공한 데이터를 얻을 수 있는 함수이다.

마찬가지로 타입을 살펴보면,

export type UseFormHandleSubmit<TFieldValues extends FieldValues, TTransformedValues extends FieldValues | undefined = undefined> = (onValid: TTransformedValues extends FieldValues ? SubmitHandler<TTransformedValues> : SubmitHandler<TFieldValues>, onInvalid?: SubmitErrorHandler<TFieldValues>) => (e?: React.BaseSyntheticEvent) => Promise<void>;

핸들러를 인자로 받는데 이 핸들러는

export type SubmitHandler<TFieldValues extends FieldValues> = (data: TFieldValues, event?: React.BaseSyntheticEvent) => unknown | Promise<unknown>;

register로 등록한 필드들이 들어있는 데이터(객체)와 event를 받는다.

글의 시작부에 첨부한 코드처럼 useFrom에 제너릭으로 준 타입과 같은 객체를 첫번째 인자로 받는 handler 함수를 만들어서 handleSubmit에 인자에 넣고 그것을 form의 onSubmit에 사용해주면 된다.

 

 

공식 문서와 내부 타입을 살펴보며 간단하게? 파헤쳐보았다.

'React' 카테고리의 다른 글

[React] Virtual DOM  (0) 2023.06.25
[React Query] Mutation 이후 캐시 데이터 직접 변경하기  (0) 2023.06.24
커링과 HOC  (0) 2023.05.31
[React-Query] 캐싱, 헷갈리는 부분  (0) 2023.05.26
컴포넌트 분리하기 전략  (0) 2023.05.24