ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] react-hook-form (Basic)
    프로그래밍/React 2023. 12. 8. 11:17

    개요

    • react-hook-form을 도입하계 된 계기
    • react-hook-form
    • 마무리

    제어 컴포넌트에서 벗어나 비제어 컴포넌트 방식으로 form을 관리한다?

    react-hook-form 도입

    라이브러리 없이 제어 컴포넌트로 form을 다루자)

     React에서 제어컴포넌트란 React를 통해서 제어하게 되는 컴포넌트이다.

     

    간단하게 이름과 아이디, 비밀번호, 전화번호, 이메일을 받는 form을 만들 때 state를 통해서 값을 관리할 것이다.

    제어컴포넌트로 form을 다룰 시에 나올 수 있는 모습이다

    더보기
    import React, { useState } from "react";
    import InputText from "../component/InputText";
    import { Label, Row } from "../style";
    
    enum Occupation {
      Student = "student",
      Professor = "professor",
    }
    
    function NoHookForm() {
      const [occupation, setOccupation] = useState(Occupation.Professor);
      const [name, setName] = useState("");
      const [id, setId] = useState("");
      const [pwd, setPwd] = useState("");
      const [phone, setPhone] = useState("");
      const [email, setEmail] = useState("");
      const [errors, setErrors] = useState({
        name: {
          invalid: false,
          message: "이름이 너무 깁니다.",
        },
        id: {
          invalid: false,
          message: "id는 3글자 이상, 20글자 이하여야 합니다.",
        },
        pwd: {
          invalid: false,
          message: "비밀번호는 10자 이하여야 합니다.",
        },
        phone: {
          invalid: false,
          message: "전화번호 형식에 맞지 않습니다.",
        },
        email: {
          invalid: false,
          message: "이메일 형식에 맞지 않습니다.",
        },
      });
    
      const handleName = (event: React.ChangeEvent<HTMLInputElement>) => {
        setName(event.currentTarget.value);
      };
      const handleId = (event: React.ChangeEvent<HTMLInputElement>) => {
        setId(event.currentTarget.value);
      };
      const handlePwd = (event: React.ChangeEvent<HTMLInputElement>) => {
        setPwd(event.currentTarget.value);
      };
      const handlePhone = (event: React.ChangeEvent<HTMLInputElement>) => {
        setPhone(event.currentTarget.value);
      };
      const handleEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
        setEmail(event.currentTarget.value);
      };
    
      const handleOccupation = (event: React.ChangeEvent<HTMLInputElement>) => {
        setOccupation(event.currentTarget.value as Occupation);
      };
    
      const handleSubmit = (event: React.SyntheticEvent) => {
        event.preventDefault();
        if (name.length > 10) {
          setErrors((prev) => ({
            ...prev,
            name: {
              ...prev.name,
              invalid: true,
            },
          }));
        }
        if (id.length < 3 || id.length > 20) {
          setErrors((prev) => ({
            ...prev,
            id: {
              ...prev.id,
              invalid: true,
            },
          }));
        }
        // ...등.. 에러 처리 이후 로직 필요.
      };
    
      return (
        <form>
          <Row>
            <Label>Occupation: </Label>
            <input
              type="radio"
              name="occupation"
              value="professor"
              defaultChecked
              onChange={handleOccupation}
            />
            <input
              type="radio"
              name="occupation"
              value="student"
              onChange={handleOccupation}
            />
          </Row>
          <Row>
            <Label>name: </Label>
            <InputText value={name} onChange={handleName} />
            {errors.name.invalid ? (
              <p className="error">{errors.name.message}</p>
            ) : null}
          </Row>
          <Row>
            <Label>id: </Label>
            <input type="text" value={id} onChange={handleId} />
            {errors.id.invalid ? (
              <p className="error">{errors.id.message}</p>
            ) : null}
          </Row>
          <Row>
            <Label>pwd: </Label>
            <input type="text" value={pwd} onChange={handlePwd} />
          </Row>
          <Row>
            <Label>phone: </Label>
            <input type="text" value={phone} onChange={handlePhone} />
          </Row>
          {occupation === Occupation.Professor ? (
            <Row>
              <Label>e-mail: </Label>
              <input type="text" value={email} onChange={handleEmail} />
            </Row>
          ) : null}
          <button onClick={handleSubmit}>Submit</button>
        </form>
      );
    }
    
    export default NoHookForm;

    더 짧게 만들 수도 있겠지만 기본적인 구성은 이렇게 될 것이다.

    validation을 비롯해 state관리 및 DX에도 상당히 불편한 점이 많고 컴포넌트 분리도 해주어야 랜더링 성능이 개선되기에 여러가지 고려사항이 더 생기는 문제가 있다.

     

    react-hook-form을 활용해보자

     

    install)

    pnpm add react-hook-form

     

    useForm)

    가장 많이 활용되는 hook으로 option에 어떠한 것이 들어가는 지 알고 있는 것이 중요하다 할 수 있다 (Docs 참고하자)
    • mode : validation 전략을 설정하는데 활용되며, 랜더링 성능과도 연관있음 (onSubmit, onChagne ...)
    • defalutValue : 초기값을 설정 (제공하지 않는다면 초기값은 undefined)
    더보기
    export type UseFormReturn<TFieldValues extends FieldValues = FieldValues, TContext = any> = {
        watch: UseFormWatch<TFieldValues>;
        getValues: UseFormGetValues<TFieldValues>;
        getFieldState: UseFormGetFieldState<TFieldValues>;
        setError: UseFormSetError<TFieldValues>;
        clearErrors: UseFormClearErrors<TFieldValues>;
        setValue: UseFormSetValue<TFieldValues>;
        trigger: UseFormTrigger<TFieldValues>;
        formState: FormState<TFieldValues>;
        resetField: UseFormResetField<TFieldValues>;
        reset: UseFormReset<TFieldValues>;
        handleSubmit: UseFormHandleSubmit<TFieldValues>;
        unregister: UseFormUnregister<TFieldValues>;
        control: Control<TFieldValues, TContext>;
        register: UseFormRegister<TFieldValues>;
        setFocus: UseFormSetFocus<TFieldValues>;
    
    };

    useForm을 통해서 return받는 값의 타입

     

    useForm return value)

    • register : 해당 함수를 통해서 input을 다룰 수 있다 form의 객체를 리턴 받아서 destructuring한다.
    • option이 다양하니깐 Docs를 보면서 도움이 될만한 것을 찾아보자!
    <input
      type="text"
      {...register("email", {
        pattern: {
          value:
            /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i,
          message: "이메일 형식에 맞지 않습니다.",
        },
      })}
    />;

     

    • formState : 에러를 비롯한 유용한 정보를 가지고 있다. 에러가 존재하지 않다면 해당 객체는 빈 객체다 된다.
    • option이 다양하니깐 Docs를 보면서 도움이 될만한 것을 찾아보자!

     

    • watch : 비제어 컴포넌트로 form을 구현할 때 조건에 따라서 필드에 노출되어야 할 때가 있다 이럴 때 사용하며 실시간으로 값을 구독하여 체크해 준다. 해당 값에 따라서 리렌더링을 일으킬 수 있다
    • 초기값을 주지 않는다면?? undefined... 
    const {id, name, pwd, ...watch} = watch(); //전체 필드를 리턴
    
    const id = watch("id");

     

    • getValues : 값을 추적할 수 있는 방법 중 리렌더링을 일으키지 않는 방법으로 watch와 비교해서 선택적으로 사용하면 됨!
    const handleEvent = (event) => {
      const value = getValues("name");
      setState(value);
    }
    // 하지만 이 코드는 state로 관리를 하니 리렌더링이 일어난다

     

    • reset : success과정에서 reset처리를 해준다면 올바른 타이밍에 초기화를 할 수 있다 reset은 이런 것을 도와준다
    useProject(id, {
      enabled: !!id,
      refetchOnMount: "always",
      onSuccess: (projectData: IProject) => {
        reset(projectData);
      },
      [reset],
    });
    useProject는 custom hook이고 서버데이터를 불러오는 함수라고 생각하면 될 듯

     

    • handleSubmit : tkdydwkr사용자가 등록버튼을 눌렀을 때 onSubmit이벤트가 발생하는데 서버에 데이터를 넘기기 전에 해당 데이터에 대한 검증을 할 수 있다고 생각하면 된다. 그렇기에 우리가 만든 onSubmit함수를 handleSubmit을 감싸게 되며 또한 event.preventDefault()도 할 필요 없다. setError와 setFocus 등 다양한 기능이 있다.
    cosnt { handleSubmit } = useForm();
    
    const onSubmit = (data: IForm) => {
      // data 가 최종 데이터
    };
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          <Row>
            ...
          <button>Submit</button>
        </form>
      );
    }

     

     

    마무리

    react-hook-form이라는 라이브러리를 통해서 DX적으로 줄어들고 개선된 코드를 작성할 수 있을 것이라고 생각이 된다.

    다음과 같은 선정이유를 기억하고 필요한 경우를 잘 따져보면서 사용해보자

    • 비제어 컴포넌트를 활용한 짧아진 코드와 좋아진 가독성, 간단한 조작 (직관적인 코드)
    • 관리해야하는 state수의 감소와 랜더링 횟수 최소화 (UI 개선 및 state로 인한 버그 최소화)
    • 라이브러리 활용에 따른 차후 유지보수 시 다른 사람들도 보기 쉬울 수 있다 (Docs가 있기에)

     

     

     

     

    참고자료 : Article , Docs

     

    '프로그래밍 > React' 카테고리의 다른 글

    [Vite] 절대경로 설정  (0) 2024.01.03
    [React] useState의 동작원리  (0) 2023.12.16
    [React] 새로운 리액트 문서에서 제시하는 9가지 권장 사항  (1) 2023.12.06
    [React] Suspense  (2) 2023.11.22
    [Hooks] useEffect  (1) 2023.11.22

    댓글

Designed by Tistory.