본문 바로가기
챌린지/패스트캠퍼스 공부 루틴 챌린지

패스트캠퍼스 챌린지 43일차 (react hook form)

by 무벅 2022. 3. 7.
반응형

22.3.7 (월) +43days

react-hook-form 은 복잡한 폼을 다룰 때 도움을 주는 라이브러리 입니다.

특징

  • 비제어 컴포넌트(Uncontrolled component) 를 사용해 불필요한 렌더링을 줄여 퍼포먼스를 향상시킵니다. (Uncontrolled Component vs Controlled Component)
  • 타입스크립트를 지원합니다.
  • React hook API 를 지원합니다.

Uncontrolled Component vs Controlled Component

react-hook-form 을 다루다보면 제어 컴포넌트(Controlled Component)비제어 컴포넌트(Uncontrolled Component)에 관한 내용이 빈번하게 소개됩니다.

react 공식 문서에서도 이를 다루고 있으나 좀 더 정확하게 이 둘의 차이를 이해하는 것이 react-hook-form 의 이해도를 높일 수 있다고 생각이 들어서 따로 소개하려고 합니다.


제어 컴포넌트 (Controlled Component)

제어된 입력은 현재값을 prop, state 로 받고 해당 값을 변경하기 위한 콜백을 받습니다.

이는 좀 더 리액트스러운 방식이라고 말할 수 있습니다.

<input value={someValue} onChange={handleChange} />

일반적으로 제어 컴포넌트는 입력을 렌더링하는 컴포넌트에서 해당 상태를 저장합니다.

물론 컴포넌트의 상태일 수 도 있고 Redux 와 같은 별도의 상태 저장소에 있을 수도 있습니다.

아래의 예시에서 새 문자를 입력할 때마다 handleNameChange 가 호출되어 입력의 새 값을 상태로 저장합니다.

때문에 입력값을 변경할 때마다 리렌더링이 일어나게 됩니다.

이러한 특징 때문에 값을 명시적으로 요청할 필요가 없이 입력 상태값이 항상 최신의 값을 갖게 됩니다.

즉, 데이터(상태)와 UI (입력) 가 항상 동기화 됩니다.

import React, { FormEvent, useState } from "react";

function App() {
  const [value, setValue] = useState<string>('');
  const [result, setResult] = useState<string>('');

  const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  }

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    setResult(value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={value} onChange={handleNameChange} />
      <input type="submit" />
      <p>{result}</p>
    </form>
  );
}

export default App;

비제어 컴포넌트 (Uncontrolled Component)

비제어된 입력은 기존 HTML 폼 인풋과 동일합니다.

const Form = () => {
  return (
    <div>
      <input type="text" />
    </div>
  )
}

사용자가 입력한 것을 기억하고, 아래의 예시와 같이 ref 를 사용하여 값을 얻을 수 있습니다.

import React, { FormEvent, useRef, useState } from "react";

function App() {
  const inputRef = useRef<HTMLInputElement>(null);

  const [result, setResult] = useState('');

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    inputRef.current && setResult(inputRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" ref={inputRef} />
      <input type="submit" />
      <p>{result}</p>
    </form>
  );
}

export default App;

즉, 필요할 때 필드에서 값을 가져와야 합니다.

일반적으로 리액트에서는 **제어 컴포넌트 (Controlled Component)**로 입력을 제어하는 것을 권하지만 react-hook-form 은 비제어 컴포넌트(Uncontrolled Component)가 불필요한 렌더링을 하지 않기 때문에 성능상 우위를 점한다는 점에서 이 방법을 사용합니다.


1. 필드 등록

react-hook-form 의 핵심 개념 중 하나는 비제어 컴포넌트 (Uncontrolled Component) 를 React hook API 에 등록하는 것 입니다.

필드를 등록하면 유효성 검사제출(submit) 모두에 해당 필드의 값을 사용할 수 있습니다.

useForm()

: useForm() 훅은 폼을 쉽게 관리하기 위한 커스텀 훅 입니다.

  • mode: ( onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'
    • 이 옵션을 사용하면 어떠한 이벤트가 발생할 때 유효성 검사를 할 것인지 설정합니다.
  • reValidateMode : (onChange | onBlur | onSubmit = 'onChange')
    • 기본적으로 유효성 검사는 입력 중에만 발생하는데, 이 옵션을 사용하면 제출(submit) 후 에러가 있는 입력이 다시 검증되는 시기를 설정할 수 있습니다.
  • defaultValues: 최초 렌더 시, 기본 값을 설정 할 수 있습니다.
  • resolver : 이 옵션은 Yup, Zod, Joi, Superstruct, Vest 등 외부 유효성 검사 라이브러리와의 통합을 지원합니다.
  • criteriaMode( firstError | all )
    • firstError(기본값)로 설정하면 각 필드의 첫 번째 오류만 수집됩니다.
    • all로 설정하면 각 필드의 모든 오류가 수집됩니다.
  • shouldFocusError : 사용자가 유효성 검사에 실패한 폼을 제출하면 에러가 있는 첫 번째 필드에 포커스가 설정될 지의 여부를 설정합니다.
  • shouldUnregister: 기본적으로 입력 값은 입력이 제거될 때 유지됩니다. 언마운트 될 때 입력을 등록 해제(unregister) 하려면 shouldUnregister 를 true 로 설정하면 됩니다.
  • delayError: 사용자에게 표시되는 에러 상태를 지연시킵니다. (ms)
type FormInputs = {
  firstName: string;
  lastName: string;
};

const { register } = useForm<FormInputs>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: false,
  delayError: undefined
})
import "./styles.css";

import React, { useState } from "react";
import { useForm } from "react-hook-form";

interface Form {
  name: string;
  password: string;
}

function App() {
  const { register, handleSubmit } = useForm();
  const [result, setResult] = useState("");

  const onSubmit = (data: Form) => setResult(JSON.stringify(data));

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" {...register("name")} placeholder="User Name" />
      <input type="password" {...register("password")} placeholder="Password" />
      <input type="submit" />
      <p>{result}</p>
    </form>
  );
}

export default App;

watch()

watch() 함수는 특정 입력을 감시하고 입력된 값을 반환합니다. 무엇을 렌더링 할지 결정하는데 사용됩니다.

첫번째 인자로 감시할 필드명, 두번째 인자로 defaultValue 를 전달합니다.

import React from "react";
import "./styles.css";
import { useForm } from "react-hook-form";

function App() {
  const { register, watch } = useForm();

  const watchNickname = watch("nickname", "Easton");

  return (
    <form>
      <input type="text" placeholder="User Name" {...register("nickname")} />
      <input type="password" placeholder="Password" {...register("password")} />
      <p style={{ color: "#fff" }}>{watchNickname}</p>
    </form>
  );
}

export default App;

2. 유효성 검사

react-hook-form 은 HTML standard for form validation 에 맞춰 유효성 검사를 쉽게 만들어줍니다.

지원되는 유효성 검증 목록

  • required (필수값)
  • min (최소값)
  • max (최대값)
  • minLength (최소 길이)
  • maxLength (최대 길이)
  • pattern (패턴)
  • validate (검증하기)
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import "./styles.css";

interface Form {
  age: number;
}

function App() {
  const { register, handleSubmit } = useForm();
  const [result, setResult] = useState<string>();

  const onSubmit = (data: Form) => {
    setResult(JSON.stringify(data));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="number"
        placeholder="Age"
        {...register("age", { required: true, min: 17 })}
      />
      <input type="submit" />
      <p style={{ color: "#fff" }}>{result}</p>
    </form>
  );
}

export default App;

3. 에러 핸들링

react-hook-form 은 폼의 에러를 표시하기 위해 errors 객체를 제공합니다. 에러 유형은 주어진 유효성 검사 조건을 반환합니다.

2. 유효성 검사 코드를 그대로 가져와서 에러 핸들링하는 부분을 추가해보도록 하겠습니다.

import "./styles.css";
import React, { useState } from "react";
import { useForm } from "react-hook-form";

interface Form {
  age: number;
}

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm();
  const [result, setResult] = useState<string>();

  const onSubmit = (data: Form) => {
    setResult(JSON.stringify(data));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="container">
        <input
          type="number"
          placeholder="Age"
          {...register("age", { required: true, min: 17, max: 40 })}
        />
        {errors.age?.type === "required" && (
          <span className="error">Age is required</span>
        )}
        {errors.age?.type === "min" && (
          <span className="error">Minimum Age is 17</span>
        )}
        {errors.age?.type === "max" && (
          <span className="error">Maximum Age is 40</span>
        )}
      </div>
      <input type="submit" />
      <p style={{ color: "#fff" }}>{result}</p>
    </form>
  );
}

export default App;

4. 중첩된 컴포넌트 구조에서 FormContext 사용하기

FormContext 는 주로 깊게 중첩된 폼 컴포넌트 구조에서 컨텍스트를 prop 으로 전달하기 어려울 때 사용합니다.

FormProvider

FormProvider 는 Provider 로 컨텍스트를 구독하는 컴포넌트들에게 FormContext 의 변화를 알리는 역할을 합니다.

FormContext 를 사용하기 위해서는 최상위 컴포넌트를 FormProvider 로 감싸주어야 합니다.

useFormContext()

useFormContext 는 FormContext 에 접근하기 위하여 사용합니다. 깊게 중첩된 컴포넌트 구조에서 컨텍스트를 구독하기 위하여 사용됩니다.

useFormContext 가 제대로 동작하기 위해선 상위 컴포넌트를 FormProvider 로 래핑해야 합니다.

infallible-yalow-kbp3r


5. 제어 컴포넌트에서 react-hook-form 사용하기

react-hook-form 은 기본적으로 비제어 컴포넌트(Uncontrolled Component) 와 HTML Input 의 입력을 수용하지만 외부 라이브러리의 제어 컴포넌트(Controlled Component) 와 함께 작업하는 것을 피할 수 없는 경우가 생기기 마련입니다.

이를 간단히 처리하기 위하여 react-hook-form 에서는 Controller 라는 래퍼 컴포넌트를 제공합니다.

import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Input } from '@material-ui/core';

interface Form {
  age: number;
}

function App() {
  const { handleSubmit, control, formState: { errors } } = useForm();
  const [result, setResult] = useState<string>();

  const onSubmit = (data: Form) => {
    setResult(JSON.stringify(data));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
     <div className="container">
       <Controller
         name="age"
         control={control}
         defaultValue={33}
         rules={{ required: true, min: 17, max: 40 }}
         render={({ field }) => <Input style={{ width: 280 }} {...field} />}
       />
       {errors.age?.type === "required" && (
         <span className="error">Age is required</span>
       )}
       {errors.age?.type === "min" && (
         <span className="error">Minimum Age is 17</span>
       )}
       {errors.age?.type === "max" && (
         <span className="error">Maximum Age is 40</span>
       )}
     </div>
      <input type="submit" />
      <p>{result}</p>
    </form>
  );
}

export default App;

 

 

 

 

 

 

 

 

 

 

https://bit.ly/37BpXiC

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다

 

 

 

 

반응형

댓글