React Hook Form

404·2022년 7월 27일
0

React.js

목록 보기
7/9
post-thumbnail

리액트로 input value를 다루는 법은 생각보다 번거롭다.

인풋을 만들고 input value가 변경될 때 값을 useState()의 상태 값에 연결해 사용한다.
(내가 배운 방식은 이러하다) 예시를 보자면 아래와 같다.

function InputComponent(){
  const [currentValue, setCurrentValue] = useState("");
  const onChange = (event:React.FormEvent<HTMLInputElement>) =>{
    const value = event.currentTarget.value;
    setCurrentValue(value);
 };
  return <input onChange={onChange} value={currentValue} />
};

어려운 방법은 아니지만 내 마음의 스승이신 이고잉 선생님께서는 항상 이렇게 말씀하셨다.

"똑같은 작업을 해야하는 것이 1억개라고 생각해보세요" -egoing

간단한 회원가입 form만 생각하더라도 이런식으로 연결해줘야 하는 input이 정말 많을 것이다.
그리고 이 것은 상당한 귀찮음을 유발하는데 오늘은 좀 더 나은 방법에 대해 배운 것을 기록해본다.

React Hook Form

https://react-hook-form.com/

리액트 훅 폼은 이러한 번거로운 작업들을 간편하게 만들어주는 라이브러리다.

사용법

  1. register & watch

    npm 에서 react-hook-form을 설치하고 useForm 을 import한다.

    useForm은 위와 같은 많은 기능들을 내장하고 있는데 form 컨트롤을 쉽게 하기 위해서 register가 쓰인다.

    import { useForm } from "react-hook-form";
    
     function InputComponent(){
      };
       const { register, watch } = useForm();
    	console.log(watch());
       return <input {...register("sampleInput")} />
     };

    코드가 훨씬 간결해졌다. input에 register 함수를 써준 것으로 useState로 시작되는 모든 상태 연결이 사라졌다. resgister 함수에 인자로 넣어준 문자열은 input의 name과 같다.

    그럼 이제 register가 어떻게 작동하는지 혹은 잘 작동하는지 확인해야한다. 이 때 watch 함수가 사용된다.

    위 사진에 보듯 watch 함수가 input value의 변화를 잘 감지하고 있다. register 함수에 인자로 넣어준 문자열을 key로, input의 value를 값으로 하는 객체가 형성된다.

    input을 더 늘려보면 한 층 더 감동적이다.

    import { useForm } from "react-hook-form";
    
    function InputComponent(){
     };
      const { register, watch } = useForm();
    	console.log(watch());
      return(
        <form>
          <input {...register("sampleInput1")} />
          <input {...register("sampleInput2")} />
          <input {...register("sampleInput3")} />
          <input {...register("sampleInput4")} />
          <input {...register("sampleInput5")} />
    	  <button> Add </button>
    	</form>
    	)
    };


    위처럼 전송할 데이터 객체 형식으로 form에 입력된 값을 바로 얻을 수 있다.

  2. handleSubmit

    form에는 대개 submit 버튼이 따라다닌다. 가장 친숙한(?) 방법은 onSubmit 이벤트에 preventDefault를 더해주고 input value들을 추출해 검증을 한 뒤 전송하는 것이다. 이 함수는 이 과정을 편하게 도와주는 역할을 한다.

    handleSubmit 함수는 위와 같이 data가 유효할 때의 동작 함수와 유효하지 않을 때의 동작 함수를 인자로 받는다.

    사용 예시를 보면 아래와 같다.

    import { useForm } from "react-hook-form";
    
    function InputComponent(){
     };
      const { register, watch, handleSubmit } = useForm();
    	const logData = (data: any) => {
        console.log(data)
      }
      return(
        <form onSubmit={handleSubmit(logData)}>
          <input {...register("sampleInput1")} />
          <input {...register("sampleInput2")} />
          <input {...register("sampleInput3")} />
          <input {...register("sampleInput4")} />
          <input {...register("sampleInput5")} />
    	  <button> Add </button>
    	</form>
    	)
    };

    실제 예시에서는 logData의 인자의 타입을 지정할 때 someDTO 같은 것이 들어가겠지만 예시이므로 any라고 명시했다.

    위 코드에서 input 값을 입력하고 버튼을 누르면 아래의 결과가 나온다.

    방금 handleSubmit 함수는 데이터의 유효성에 따라 두 가지 함수를 인자로 받을 수 있다고 했다. 그렇다면 유효하지 않은 데이터를 전송하려 시도했을 때는 어떻게 될까?

    import { useForm } from "react-hook-form";
    
    function InputComponent(){
     };
      const { register, watch, handleSubmit } = useForm();
    	const logData = (data: any) => {
        console.log(data)
      }
      return(
        <form onSubmit={handleSubmit(logData)}>
          <input {...register("sampleInput1", {required : true})} />
          <input {...register("sampleInput2", {required : true})} />
          <input {...register("sampleInput3", {required : true})} />
          <input {...register("sampleInput4", {required : true})} />
          <input {...register("sampleInput5", {required : true})} />
    	  <button> Add </button>
    	</form>
    	)
    };

    모든 input을 필수 입력 상태로 만들었다. 여기서 보통 사용했던 방식과 다른 것은 input 태그에 required 속성을 추가할 수 있음에도 불구하고 register 함수의 인자로서 전달했다는 것이다. (minLength : 5 와 같은 제한들도 , 로 구분하여 전달 할 수 있다.

    HTML에 대해서 공부를 조금 했을 때 필자는 네이버의 HTML 소스를 수정해 친구들을 놀려먹는 장난을 자주 쳤는데 ... input 태그에 추가한 required 속성은 이와 같은 방식으로 간단하게 제거된다. 전송 단계에서 추가적인 검증 작업을 하지 않거나 서버에서 검증작업이 없다면 완전히 쓸모없는 데이터가 저장되거나 혹은 서버 에러를 유발할 수도 있다. 따라서, 위 예시와 같이 HTML이 아닌 javascript를 사용한 보호 수단은 react-hook-form이 주는 큰 장점이라고 하겠다.


    현재는 유효하지 않은 데이터에 대한 함수를 넣어주지 않았기 때문에 콘솔에 아무런 일도 발생하지 않는다. 5번 input의 값을 입력하고 Add 버튼을 눌렀을 때 자동으로 1번 input이 포커스된다. (필수 작성이지만 비어있는 첫 번째 input으로)

    유효하지 않은 input 데이터를 처리하는 예시를 보자. 이 때는 formState를 활용한다.

    formState의 errors 를 콘솔로 확인하면 유효하지 않은 데이터를 전송할 시 에러의 상태를 확인할 수 있다.

    위처럼 sampleInput1이 required 를 어겼다는 상태를 나타내준다.
    아래는 좀 더 활용적인 예시를 나타낸 코드이다.

    import { useForm } from "react-hook-form";
    
    function InputComponent(){
     };
      const { register, watch, handleSubmit, formState } = useForm();
    	const logData = (data: any) => {
        console.log(data)
      }
      
      return(
        <form onSubmit={handleSubmit(logData)}>
          <input {...register("sampleInput1", {required : "이 필드를 반드시 입력하세요"})} />
          <input {...register("sampleInput2", {required : true})} />
          <input {...register("sampleInput3", {required : true})} />
          <input {...register("sampleInput4", {required : true})} />
          <input {...register("sampleInput5", {required : true})} />
    	  <button> Add </button>
    	</form>
    	)
    };

    위 사진에서 본 에러상태 객체에는 message 라는 속성이 있다. 만약 유효하지 않은 데이터를 전송하려고 하면 해당 객체에 메시지를 전달해 주는데 위의 예시코드처럼 하는 것이 가능하다.

    그럼 같은 에러에 대한 상태 객체를 살펴보자

    상태에 메시지가 추가됐다. 이렇게 상태 메시지를 통해 사용자에게 경고 메시지를 더 쉽게 전달 할 수 있다.

    마지막으로 가장 현실적인 예제를 보자.

    import { useForm } from "react-hook-form";
    
     function InputComponent(){
      };
       const { register, watch, handleSubmit, formState } = useForm();
    	const logData = (data: any) => {
         console.log(data)
       }
       
       return(
         <form onSubmit={handleSubmit(logData)}>
           <input {...register("sampleInput1", {required : "이 필드를 반드시 입력하세요"})} />
           <input {...register("sampleInput2", {required : true})} />
           <input {...register("sampleInput3", { required: true, minLength: 5 })} />
           <input {...register("sampleInput4", 
               required: "이 필드를 반드시 입력하세요",
               minLength: { value: 5, message: "5자 이상 입력하세요" },
             })} />
           <input {...register("sampleInput5", {required : true})} />
    	  <button> Add </button>
    	</form>
    	)
     };

    여러가지 유효성 검사가 추가되었다. 결론적으로 볼 것은 4번 인풋이다.
    이 인풋의 값이 입력되지 않았다면 required 에러 타입과 함께 "반드시 입력하세요" 메시지가 나온다. 입력이 되었지만 5자 미만이라면 minLength 에러 타입과 함께 "5자 이상 입력하세요" 메시지가 나올 것이다.

profile
T.T

0개의 댓글