Controller (react-hook-form)

Ryan Cho·2024년 12월 9일
1

Controller

Controller컴포넌트는 외부 컨트롤된 컴포넌트 (AntD, Material-UI 등)와 함께 작동하기 위해 설계된 래퍼 컴포넌트인데, 기본적으로 react-hook-form은 비제어 컴포넌트를 사용하는 방식이지만 외부라이브러리 컴포넌트는 제어컴포넌트임.
그래서 Controller는 제어 컴포넌트를 쓰기위한, 쓸때 다시 화면에 렌더링을 하기 위한 컴포넌트.

antd의 Select컴포넌트와 Controller

import { Select } from "antd";
import { FC } from "react";
import { Controller, useController, useFormContext } from "react-hook-form";
import RHForm from "./RHForm";
import { z } from "zod";
import { ErrorMessage } from "@hookform/error-message";

type OptionsType = {
  value: string;
  label: string;
};

type RHSelect1Props = {
  options: OptionsType[];
  name: string;
};

const RHSelect1: FC<RHSelect1Props> = ({ options, name }) => {
  return (
    <Controller
      name={name}
      render={(methods) => {
        return (
          <>
            <Select
              options={options}
              onChange={methods.field.onChange}
              onBlur={methods.field.onBlur}
              value={methods.field.value}
              ref={methods.field.ref}
            />
            <ErrorMessage name={name} errors={methods.formState.errors} />
          </>
        );
      }}
    />
  );
};


const options: OptionsType[] = [
  { value: "1", label: "1" },
  { value: "2", label: "2" },
  { value: "3", label: "3" },
];

const schema = z.object({
  select1: z.string({
    invalid_type_error: "선택해주셈",
    required_error: "선택해주셈",
  }).optional()
});
type FormType = z.infer<typeof schema>;


const defaultValues: FormType = {
    select1:undefined,
    select2:undefined
}

const ResetButton = () => {
  const methods = useFormContext<FormType>(); // 현재 폼 컨텍스트에 접근

  return (
    <button
      type="button"
      onClick={() => methods.reset(defaultValues)}
    >
      reset
    </button>
  );
};

const SelectTest = () => {
  return (
    <RHForm<FormType> // 엄격한 타입처리
      schema={schema}
        defaultValues={defaultValues}
    >
      <RHSelect1 options={options} name="select1" />
      <button type="submit">Submit</button>
      <ResetButton />
    </RHForm>
  );
};

export default SelectTest;

Controller의 name속성은 폼 필드의 name과 연결되고,
이전 포스팅에 사용한 RHForm 컴포넌트에 FormProvider속성으로 인해, Controller의 render속성에 field객체를 사용할 수 있다.

Controller의 onChange속성은 form의 해당 필드값을 업데이트 하는데 사용

useController

JSX내에 직접사용하는 Controller컴포넌트와 달리, useController는 훅으로서 사용한다.
두 방법 모두 폼 필드 관리라는 목적을 가지미나 useController는 더 많은 커스터마이징 기능을 가능하게하여 유연성이 높다는 차이정도가 있다.

Select예제의 useController 버전

...

const RHSelect2: FC<RHSelect1Props> = ({ options, name }) => {
  const methods = useController({
    name,
  });

  return (
    <>
      <Select
        options={options}
        onChange={methods.field.onChange}
        onBlur={methods.field.onBlur}
        value={methods.field.value}
        ref={methods.field.ref}
      />
      <ErrorMessage name={name} errors={methods.formState.errors} />
    </>
  );
};

const schema = z.object({
  select1: z.string({
    invalid_type_error: "선택해주셈",
    required_error: "선택해주셈",
  }).optional(),
  select2: z.string({
    invalid_type_error: "선택해주셈",
    required_error: "선택해주셈",
  }).optional(),
});
type FormType = z.infer<typeof schema>;

const SelectTest = () => {
  return (
    <RHForm<FormType>
      schema={schema}
        defaultValues={defaultValues}
    >
      <RHSelect1 options={options} name="select1" />
      <RHSelect2 options={options} name="select2" />
      <button type="submit">Submit</button>
      <ResetButton />
    </RHForm>
  );
};

두 방법 모두 가능하지만, Controller컴포넌트가 선언적 프로그래밍 방식에 좀 더 적합해서 나는 해당 방식을 더 선호한다.

profile
From frontend to fullstack

0개의 댓글