TIL

[TIL/React-Hook-Form] 2024/07/09

reference:1) https://www.react-hook-form.com/api/usecontroller/controller2) https://legacy.reactjs.org/docs/render-props.html✅ React-Hook-Fo

2024년 7월 9일8min read

reference: 1) https://www.react-hook-form.com/api/usecontroller/controller 2) https://legacy.reactjs.org/docs/render-props.html

✅ React-Hook-Form 공식문서 톺아보기

6. Integrating with UI libraries ✍️

code
import Select from "react-select";
import { useForm, Controller } from "react-hook-form";
import Input from "@material-ui/core/Input";

const App = () => {
  const { control, handleSubmit } = useForm({
    defaultValues: {
      firstName: '',
      select: {}
    }
  });
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="firstName"
        control={control}
        render={({ field }) => <Input {...field} />}
      />
      <Controller
        name="select"
        control={control}
        render={({ field }) => <Select 
          {...field} 
          options={[
            { value: "chocolate", label: "Chocolate" },
            { value: "strawberry", label: "Strawberry" },
            { value: "vanilla", label: "Vanilla" }
          ]} 
        />}
      />
      <input type="submit" />
    </form>
  );
};

외부 UI 라이브러리와 React Hook Form을 어떻게 통합해야 하는지 설명하는 section이다.

If the component doesn't expose input's ref, then you should use the Controller component, which will take care of the registration process.```

Controller 컴포넌트를 사용하면, 외부 UI 라이브러리의 요소의 registration process를 잘 수행할 수 있다고 설명하고 있다.

아래의 코드가 본 section의 핵심이다. 하지만 공식 문서의 Get Started 파트에서는 피상적인 사용법만 다루고 있기에, Controller Component의 더 깊은 내용을 확인해야 했다.

} />

code

![](https://velog.velcdn.com/images/minkwan/post/b3242a84-2a46-4e4d-8af6-e9c9dc23195b/image.png)

external controlled component와 쉽게 통합하기 위해 사용하는 'Wrapper Component'임을 명시하고 있다.

![](https://velog.velcdn.com/images/minkwan/post/d76c40a0-1189-48fd-a9b4-fa4bc7fef750/image.png)

render props가 핵심인데, render는 컴포넌트에 event와 value를 연결할 수 있는 기능을 제공한다. 자식 컴포넌트에 onChange, onBlur, name, ref, value를 제공하며, 특정 '입력 상태'를 포함하는 fieldState 객체도 제공한다. 

render의 type은 'Function'이다. 그런데 ```render={({ field }) => <Input {...field} />}``` 부분이 솔직히 잘 이해가 되지 않았다. 이유를 알아보니, render props가 무엇인지 몰라서 그랬던 것이었다.(React 공식 문서 읽어야 한다는 뜻.)

'render prop'은 함수이고, Controller 컴포넌트에 의해 호출되도록 내부적으로 구현이 되어있다. field 객체를 포함한 다양한 인자를 받을 수 있는 함수인데, 위 코드에서는 field를 구조 분해 할당을 통해 추출한다. 이때, field 객체는 Controller 컴포넌트에 의해 자동으로 제공되는 'form field와 관련된 상태와 메서드'를 포함한다. 이렇게 추출한 field 객체를 하위 컴포넌트(MUI Component 같은)에 전달하여 외부 컴포넌트 요소가 form field의 상태와 상호작용할 수 있게 되는 것이다.

![](https://velog.velcdn.com/images/minkwan/post/6ec7dc0e-de87-42dd-998c-262c4d8f3764/image.png)

과거 React doc을 살펴보니 커스텀 훅으로 대체되었다고 한다. 예전 공식 문서에서 설명하는 것처럼, render prop은 react 컴포넌트 간에 코드를 공유하기 위한 기법인데, custom hook의 존재 이유와 정확히 일치하기 때문이다. custom hook의 시초 같은 느낌이다.

## 7. Integrating Controlled Inputs ✍️

import { useForm, Controller } from "react-hook-form"; import { TextField, Checkbox } from "@material-ui/core";

function App() { const { handleSubmit, control, reset } = useForm({ defaultValues: { checkbox: false, } }); const onSubmit = data => console.log(data);

return (

} /> ); }

code

계속 공식 문서를 읽다 보니, Controlled component와 Uncontrolled component가 핵심이라는 생각이 든다.

Controlled component는 React의 상태(state)에 의해 값이 제어됨을 의미한다. 한마디로, 사용자가 입력 form을 조작할 때마다 React 상태가 업데이트되며, 이에 따라 UI가 리렌더링 된다는 뜻이다.

이에 반해, Uncontrolled component는 상태에 의존하지 않고 DOM 요소의 ref를 통해 값에 접근한다. 사용자의 입력에 따라 DOM 요소의 값이 변화할지언정, React의 상태는 이를 추적하지 않기에, 렌더링이 일어나지 않아 렌더링을 최적화할 수 있다는 장점을 갖는다.

각각의 장단점이 분명하겠지만, 사용자로부터 많은 정보를 입력받을 경우에는 불필요한 리렌더링을 최적화하는 것이 좋고, 이것이 React Hook Form의 가장 본질적인 컨셉인 것이다.

import React, { useState } from "react"; // React와 useState 훅을 import합니다. import ReactDOM from "react-dom"; // ReactDOM을 import합니다. import { useForm, Controller } from "react-hook-form"; // react-hook-form에서 useForm과 Controller를 import합니다. import Header from "./Header"; // Header 컴포넌트를 import합니다. import ReactDatePicker from "react-datepicker"; // react-datepicker 컴포넌트를 import합니다. import NumberFormat from "react-number-format"; // react-number-format 컴포넌트를 import합니다. import ReactSelect from "react-select"; // react-select 컴포넌트를 import합니다. import Mui from "./Mui"; // Mui 컴포넌트를 import합니다. import ButtonsResult from "./ButtonsResult"; // ButtonsResult 컴포넌트를 import합니다. import DownShift from "./DownShift"; // DownShift 컴포넌트를 import합니다. import AntD from "./AntD"; // AntD 컴포넌트를 import합니다. import DraftExample from "./DraftExample"; // DraftExample 컴포넌트를 import합니다. import { EditorState } from "draft-js"; // draft-js의 EditorState를 import합니다. import InputMask from "react-input-mask"; // react-input-mask 컴포넌트를 import합니다. import Chakra from "./Chakra"; // Chakra 컴포넌트를 import합니다. import "react-datepicker/dist/react-datepicker.css"; // react-datepicker의 CSS 파일을 import합니다. import "antd/dist/antd.css"; // antd의 CSS 파일을 import합니다. import "./styles.css"; // 스타일 CSS 파일을 import합니다.

let renderCount = 0; // 렌더링 횟수를 저장하는 변수를 초기화합니다.

const defaultValues = { Native: "", // Native 입력 필드의 기본값을 설정합니다. TextField: "", // TextField 입력 필드의 기본값을 설정합니다. Select: "", // Select 입력 필드의 기본값을 설정합니다. ReactSelect: { value: "vanilla", label: "Vanilla" }, // ReactSelect 컴포넌트의 기본 선택값을 설정합니다. Checkbox: false, // Checkbox 입력 필드의 기본값을 설정합니다. switch: false, // switch 입력 필드의 기본값을 설정합니다. RadioGroup: "", // RadioGroup 입력 필드의 기본값을 설정합니다. numberFormat: 123456789, // numberFormat 입력 필드의 기본값을 설정합니다. AntdInput: "Test", // AntdInput 입력 필드의 기본값을 설정합니다. AntdCheckbox: true, // AntdCheckbox 입력 필드의 기본값을 설정합니다. AntdSwitch: true, // AntdSwitch 입력 필드의 기본값을 설정합니다. AntdSlider: 20, // AntdSlider 입력 필드의 기본값을 설정합니다. AntdRadio: 1, // AntdRadio 입력 필드의 기본값을 설정합니다. downShift: "apple", // downShift 입력 필드의 기본값을 설정합니다. ReactDatepicker: new Date(), // ReactDatepicker 컴포넌트의 기본 날짜값을 설정합니다. AntdSelect: "", // AntdSelect 입력 필드의 기본값을 설정합니다. DraftJS: EditorState.createEmpty(), // DraftJS 에디터의 기본 상태를 설정합니다. MUIPicker: new Date("2020-08-01T00:00:00"), // MUIPicker 컴포넌트의 기본 날짜값을 설정합니다. country: { code: "AF", label: "Afghanistan", phone: "93" }, // country 객체의 기본값을 설정합니다. ChakraSwitch: true, // ChakraSwitch 입력 필드의 기본값을 설정합니다. reactMaskInput: "" // reactMaskInput 입력 필드의 기본값을 설정합니다. };

function App() { const { handleSubmit, reset, setValue, control } = useForm({ defaultValues }); // useForm 훅을 사용하여 폼 상태를 초기화하고 제어할 수 있는 객체들을 가져옵니다. const [data, setData] = useState(null); // data 상태와 setData 함수를 useState 훅을 통해 초기화합니다. renderCount++; // 렌더링 횟수를 증가시킵니다.

return (

setData(data))} className="form"> // 폼을 제출할 때 handleSubmit 함수를 호출하여 데이터를 설정하고, CSS 클래스 "form"을 적용합니다.
// Header 컴포넌트를 렌더링하고, renderCount를 prop으로 전달합니다.

// Mui 컴포넌트를 렌더링하고, control prop으로 useForm에서 가져온 컨트롤 객체를 전달합니다.


// 수평선을 추가합니다.

// AntD 컴포넌트를 렌더링하고, control prop으로 useForm에서 가져온 컨트롤 객체를 전달합니다.


// 수평선을 추가합니다.

// CSS 클래스 "container"를 가진 div를 생성합니다.
// 섹션을 시작합니다. // 라벨을 추가합니다. ( // render prop을 통해 field 객체를 받아 ReactSelect 컴포넌트를 렌더링합니다. )} />

// 섹션을 시작합니다. // 라벨을 추가합니다. ( // render prop을 통해 field 객체를 받아 ReactDatePicker 컴포넌트를 렌더링합니다. field.onChange(e)} // onChange 이벤트를 설정하고 field.onChange 함수를 호출합니다. selected={field.value} // 선택된 날짜 값을 설정합니다. /> )} />

// 섹션을 시작합니다. // 라벨을 추가합니다. ( // render prop을 통해 field 객체를 받아 NumberFormat 컴포넌트를 렌더링합니다. // NumberFormat 컴포넌트에 필드 속성을 전달합니다. )} name="numberFormat" // 폼 데이터의 이름을 설정합니다. className="input" // CSS 클래스 "input"을 추가합니다. control={control} // useForm에서 가져온 컨트롤 객체를 전달합니다. />

// 섹션을 시작합니다. } // render prop을 통해 필드 객체에서 ref를 제외한 나머지 속성을 전달하여 DownShift 컴포넌트를 렌더링합니다. control={control} // useForm에서 가져온 컨트롤 객체를 전달합니다. name="downShift" // 폼 데이터의 이름을 설정합니다. />

// 섹션을 시작합니다. // 라벨을 추가합니다. // DraftExample 컴포넌트를 렌더링하고, control prop으로 useForm에서 가져온 컨트롤 객체를 전달합니다.

// 섹션을 시작합니다. // 라벨을 추가합니다. ( // render prop을 통해 필드 객체에서 onChange와 value를 받아 React Input Mask 컴포넌트를 렌더링합니다. // InputMask 컴포넌트에 마스크와 값을 설정하고 onChange 이벤트를 처리합니다. {(inputProps) => ( // 랜더링 함수를 통해 input 요소에 inputProps를 전달합니다. )} )} />


// 수평선을 추가합니다.

// Chakra 컴포넌트를 렌더링하고, control prop으로 useForm에서 가져온 컨트롤 객체를 전달합니다.

// ButtonsResult 컴포넌트를 렌더링하고, data, reset, setValue를 props로 전달합니다. ); }

const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); // App 컴포넌트를 root 요소에 렌더링합니다.