React Hook Form | 내가 실무에서 쓰는 법

    반응형

    안녕하세요.
    오늘은 정말! 유용한 라이브러리 React Hook Form에 관해 기본적인 내용들을 정리해보려고 합니다
    이젠.. 이거 없으면 개발하기 정말 귀찮을 정도로... 너무 유용한 라이브러리입니다
    거의 리액트 개발에선 필수가 아닐까 싶을정도!
    공홈 자체에서도 한국어를 지원해주고 있지만 필드가 많아지거나 하면 복잡해지는게 꽤나 있더라고요
    그래서 제가 실무에서 써먹던 방법도 담아볼까합니다
    공홈의 있는 모든 내용들을 다루진 않고 제가 실무에서 썼던  API 위주로 설명하겠습니다



     


     

     

     

    1.  React Hook Form 이란?

    폼, 양식을 만들때 사용하는 라이브러리

    해당 라이브러를 사용하지 않더라면?

    import React, { useState } from 'react';
    
    const InputSample = () => {
      const [text, setText] = useState('');
    
      const onChange = (e) => {
        setText(e.target.value);
      };
    
      return (
        <>
          <input onChange={onChange} value={text}  />
          <div>
           	{text}
          </div>
        </>
      );
    }
    
    export default InputSample;

    이런식으로 입력된 값을 실시간으로 감지하고, 그걸 상태에 담아줘야하는 로직을 짜야했습니다

    근데 React Hook Form을 사용한다면?

    import { useState } from "react";
    import { useForm } from "react-hook-form";
    
    export function App() {
      const { register, handleSubmit } = useForm();
    
      const onSubmit = (data) => console.log(data);
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          <input {...register("name")} placeholder="이름" />
          <input {...register("password")} placeholder="비밀번호" />
          <button type="submit">제출</button>
        </form>
      );
    }

    이렇게 작성해주고 콘솔로 데이터를 확인해보면
    input에 작성한 value들이 각각 name, password key에 담긴걸 확인 할 수 있습니다

    이처럼 코드 양이 확 줄어들기도 하고, 무엇보다 데이터를 뿌려줄때가 제일 유용하더라고요

    그럼 어떤 API들이 있는지 봅시다!

     

     

    2. API: useForm

    대부분 form에 필요한 hook들은 useForm API에서 불러와서 사용할 수 있다

    const { /.../ } = useFrom();
    • register : input에서 값을 불러오기 위한 함수. 유효성 검사도 쉽게 적용 가능하며, onChange 같은 이벤트를 사용할 필요 없이 입력한 값이 value로 적용된다.
    <input type="text" placeholder="username" {...register("사용하고 싶은 이름"} />
    • unregister : register의 반대말로 데이터 목록에서 해당 input의 key-value를 지울때 사용됩니다. 데이터를 제출 하기전에 삭제하거나 그럴때 유용하게 사용될 것 같습니다
    • formState : react hook form으로 관리해주고 있는 해당 양식의 상태에 대한 정보를 포함합니다. 메서드가 몇몇개 있는데 그 중 제가 실무에서 썼던건 isDirty입니다
    const { formState: { isDirty } } = useForm({ defaultValues: { test: "" } });
    
    <button disabled={!isDirty}>

    꼭 defaultValues가 있어야지 가존 양식에서 변화가 일어났는지를 감지합니다
    그래서 변화가 일어나면 isDirty 는 true가 되면서 버튼이 활성화 되고, 양식이 다시 원래 값으로 되돌아가면 false가 되면서 버튼은 비활성화됩니다

    💥 근데 생각보다 복잡한 양식에서는 이게 만능은 아닙니다!
    값이 기본값으로 되돌아 갔는데도 계속 true만 토해내는 경우가 있더라고요

    • watch : 지정된 input의 값 변경을 감지해서 반환해준다. 렌더링 할 대상을 결정할때 유용하다
      아래 예제와 같이 name 또는 register으로 지정한 해당 input의 값 변경을 감지할 수 있다
    const isRate = watch('promotion_type') === 'rate'

    이거 정말 유용합니다! 정말 여기저기 활용할 곳이 많은 만능!
    저 같은 경우에는 datepicker에서 종료일을 선택했을 경우 시작일 캘린더에서 종료일 이후를 선택하지 못하게 막는걸 watch를 통해 해결한 적도 있고
    checkbox 를 선택했냐 안했냐에 따라 어떤 글자를 달리 보여줘야할때 쓰기도하고... 등등 정말 유용해요!

    • handleSubmit : 이 함수는 유효성 검사가 완료 되었을때 폼 데이터를 전달. form 제출 시 새로고침 현상도 방지 (event.preventDefault(); 역할)을 해줌
    <form onSubmit={handleSubmit(onSubmit)}>
    	/.../
    </form>
    • reset, resetField : 양식 값을 비워줍니다. defaultValues가 있으면 해당 값을 참고합니다
    • setError : 입력 오류를 수동으로 설정합니다
    • clearErrors : 양식의 오류를 수동으로 지웁니다.
      모든 오류, 단일 오류, 여러 오류 이렇게 3가지 방법으로 처리하나 봅니다
    • setValue : 등록된 필드의 값을 동적으로 설정하고, 양식 상태를 확인하고 업데이트 하는 옵션을 가질 수 있습니다
      해당 input과 name이 일치하면 value를 할당해줍니다
      저는 복잡한 데이터의 경우 값을 거르고(?) 넣어야할 때가 있는데 그럴때 유용하게 사용하기도 했고,
      제출 후 초기값을 갱신해야하는 경우에도 쓰고.... 이래저래 유용합니다
      const { data } = useQuery(["test"], fetchData, {
        onSuccess: (data) => {
          const { started_at, items, ended_at } = data;
    
          setValue("started_at", new Date(started_at));
          setValue("ended_at", new Date(ended_at));
    
          items.map((data: any) =>
            Object.entries(data).forEach(([key, value]: any) => {
              if (["Adate"].includes(value)) {
                setValue("A.data", data.data);
              }
            })
          );

    위 코드같은 형태로 많이 썼었습니다

    • setFocus : 수동으로 입력 초점을 설정합니다 👉 이 내용은 찐으로 쓸 일이 없어서.. 뭐에 유용한건지 잘 모르겠네요
    • getValues : 해당 값 가져오기. 양식 값을 하나하나 수동으로 가져오는 형태인 것 같습니다
    • getFieldState : 해당 필드의 상태 값들을 반환합니다. formState랑 비슷한 것 같은데, 이건 수동으로 지정한(?) 필드의 상태를 반환할 수 있는 것 같습니다
    • trigger : 유효성 감사를 수동으로 트리거합니다 👉 이 부분도 쓸 일이 없어서 잘 모르겠습니다
    • control : Controller API 같이 쓴다. 안에는 제어되는 컴포넌트를 React Hook From에 등록하기 위한 메서드가 담겨있다 (console.log로 찍어보면 쓸 수 있는 메서드들이 나열됨)
      register 대신 사용하는 객체인걸로 판단됩니다
    <Controller 
    	name="user"
    	control={control}
    />

     

     

     

    3. API: Controller

    CSS 라이브러리 같은 것들과 조합하며 쉽게 사용할 수 있게 해주는 wrappr 컴포넌트

    <Controller
      name="description"
      control={control}
      defaultValue=""
      rules={{ required: true }}
      render={({
    	field: { onChange, onBlur, value, ref },
    	fieldState: { error },
      }) => (
      	<Input
    	  onChange={onChange}
          onBlur={onBlur}
          elementRef={ref}
          value={value}
          size="md"
          required
        />
      )}
    />
    • name : input의 교유 이름
    • control : useForm 호출 후 리턴된 값 중 하나. 위에 설명한 값이랑 control이랑 같습니다
    • as : as로 전달되는 컴포넌트에 onChange, onBlur 그리고 value props를 주입한다 (render보다 사용하기 까다롭다)
      V7 버전에서는 사라졌습니다
    • render : React 엘리먼트를 리턴하는 함수. 컴포넌트에 이벤트나 값을 결합할 수 있다. onChange, onBlur, value, 같은 표준적인 이름을 사용하지 않는 외부 컴포넌트에 쉽게 적용할 수 있다
    • defaultValue : input defaultValue와 동일함. boolean 값이 주어졌을때는 체크박스 입력 값으로 다루어짐
      • defaultValue 값을 직접 주입하거나 useForm을 호출할때 defaultValue 값을 넣어줘야한다
      • 만약 기본 값과 함께 reset을 호출한다면, 인라인 값으로 defaultValue를 제공하는 대신 useForm을 호출할때 defaultValue를 제공해줘야한다
    • rules : register를 호출할 때 지정하는 유효성 검사 규칙과 같은 포맷으로 넣어준다
    • onFocus : 유효성 검사 에러가 발생했을때 이 콜백을 이용하여 특정 input으로 포커스를 자동으로 이동시켜준다
      V7 버전에서는 사라졌습니다

     

     

     

    4. API: useFieldArray

    필드 배열 작업을 위한 hook
    이거 정말 세상 유용합니다... 여러개의 input을 포함한 field를 만들때 매우매우 유용합니다!
    제가 현재 개발하는 UI에서는 이게 꼭 필요해서 요즘은 요것만 쓰는중

    import React from "react";
    import { useForm, useFieldArray } from "react-hook-form";
    
    function App() {
      const { register, control, handleSubmit, reset, trigger, setError } = useForm({
        // defaultValues: {}; you can populate the fields by this attribute 
      });
      const { fields, append } = useFieldArray({
        control,
        name: "test"
      });
      
      return (
        <form onSubmit={handleSubmit(data => console.log(data))}>
          <ul>
            {fields.map((item, index) => (
              <li key={item.id}>
                <input {...register(`test.${index}.firstName`)} />
                <Controller
                  render={({ field }) => <input {...field} />}
                  name={`test.${index}.lastName`}
                  control={control}
                />
                <button type="button" onClick={() => remove(index)}>Delete</button>
              </li>
            ))}
          </ul>
          <button
            type="button"
            onClick={() => append({ firstName: "bill", lastName: "luo" })}
          >
            append
          </button>
          <input type="submit" />
        </form>
      );
    }

    위 코드는 공홈에서 긁어왔는데
    뭔가 항목 형태로 여러개의 input 목록들을 추가하고, 삭제하고 유동적으로 관리해야하는 경우 씁니다


    그리고 여기서 유의할 점은 field가 여러개라면 해당 field를 구별해줘야하는 맵핑 key가 있어야합니다

    // 잘못됨
    defaultValues : {
        {
            "type": "data1",
            "title": "목록1",
            "data": [
                {
                    "question": "",
                }
            ]
        },
        {
            "type": "data2",
            "title": "목록2",
            "data": [
                {
                    "question": ""
                }
            ]
        },
        {
            "type": "data3",
            "title": "목록3",
            "data": [
                {
                    "question": ""
                }
            ]
        }
    }
    
    // 올바름
    defaultValues : {
        "test1": {
            "type": "data1",
            "title": "목록1",
            "data": [
                {
                    "question": "",
                }
            ]
        },
        "test2": {
            "type": "data2",
            "title": "목록2",
            "data": [
                {
                    "question": ""
                }
            ]
        },
        "test3": {
            "type": "data3",
            "title": "목록3",
            "data": [
                {
                    "question": ""
                }
            ]
        }
    }

    field를 구별해주는 맵핑 key가 없으면 useFieldArray의 name을 정할 수가 없고 에러를 뱉어냅니다...
    그래서 위처럼 test1, test2, test3 처럼.. 감싸줘야하고... (그래서 데이터 제출할땐 별도의 핸들링이 필요해짐)
    또한 data라고 써져있는 key에 input name들?이 다 적혀져있어야지 append가 잘 먹힌다는 점...?
    안그러면 데이터 제출할때 엉뚱한 key가 들어가있거나... 등등 맵핑이 잘 안됩니다
    한마디로 useFieldArray를 쓸때는 데이터 맵핑을 잘~ 해야한다는거!
    그리고 단순 string[]이 불가능합니다 꼭 key-value 쌍을 이뤄야하더라고요 ㅠㅠㅠ
    (이거때문에 데이터 핸들링때 애 좀 먹음...)

    단순 텍스트로 설명하기가 좀 힘드네요 실무 코드를 까기엔 너무 복잡해서.... 별도 설명 없이는 불가능

    그리고 이렇게 field가 여러개로 많아지면 한 파일에 써야하는 코드 양이 매우 길어지는데...
    이렇게 되면 슬슬 코드 분리를 하게됩니다
    그렇게 되면.......! 이 것들을 어떻게 관리하지? 데이터가 하나로 잘 뭉쳐지나??? 하고
    엄청난 고민에 휩싸이고 살짝의 멘붕이 터지는데 그때 제공해주는 hook이 있습니다 

     

     

     

     

    5. API: useFormContext

    React Hook Form을 context로 사용할 수 있습니다

    // root
    const method = useForm({
        mode: "onSubmit",
        defaultValues: sampleDetaultValues,
     });

    데이터가 합쳐지는 root 파일에 위와 같이 코드를 작성해줍니다

    // 찢어진 파일들
    const { control } = useFormContext();

    그리고 분리한 각각의 컴포넌트에 위 코드를 선언해주고 input도 아래와 같은 형태로 바꿔줘야합니다

    // 전
     <TextInput {...register(`test.data.${index}.question`)} />
    
    
    // 후
    <Controller
      control={control}
      name={`test.data.${index}.question`}
      render={({ field }) => {
        return (
          <TextInput
            {...field}
          />
        );
      }}
    />

    이유는 모르겠으나.. 꼭 Controller를 써야하더라고요
    안그러면 해당 input 값이 아예 안 넘어가요
    이렇게하면 값들이 알아서 한대 잘~ 모아져서 제출되고! 값도 알아서 해당 input 값에 잘 박혀져서 뿌려집니다!

    그리고 root에서 watch, setValue 어케 쓰냐! 싶으시다면
    method.watch(), method.setValue, method.formState.isDirty 이런식으로 쓰시면 됩니다

     

     

     

    6. 마무리

    useFormState, useWatch 는 제가 써본적도 없고 공홈을 읽어도 잘... 뭐에 유용할지도 모르겠어서 따로 작성하진 않았습니다
    저는 이제 React Hook Form 없이는 개발 불가능 (농담) 지경에 이른것 같습니다^^ㅋㅋㅋㅋ
    아직 안 써보신분들 계시다면 이번에 꼭 써보세요~

    반응형

    댓글