forwardRef + useImperativeHandle + Controller 사용법

ddoachi·2025년 5월 2일

TekaPicker

목록 보기
16/30

forwardRef + useImperativeHandle + Controller 사용법 정리

1. forwardRef란?

React에서 ref는 기본적으로 DOM 요소에만 연결할 수 있음
컴포넌트 자체에 ref를 연결하려면 forwardRef로 감싸야 함

const MyComponent = forwardRef((props, ref) => {
  // ref 사용 가능
});

2. useImperativeHandle이란?

useImperativeHandle은 전달받은 ref에 대해 외부에서 호출할 수 있는 메서드나 값을 정의할 수 있도록 해줌.

useImperativeHandle(ref, () => ({
  focusInput: () => {
    inputRef.current?.focus();
  }
}));

이 방식은 컴포넌트 내부 구현을 숨기고, 외부에 필요한 기능만 노출하는 데 유용.

3. forwardRef + useImperativeHandle 전체 예시

상위 컴포넌트

const dialogRef = useRef<DialogRefType>(null);

const handleClick = () => {
  dialogRef.current?.focusMenuName();
};

return <CreateMenuDialog ref={dialogRef} />;

하위 컴포넌트

interface DialogRefType {
  focusMenuName: () => void;
}

const CreateMenuDialog = forwardRef<DialogRefType, Props>((props, ref) => {
  const menuNameInputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focusMenuName: () => {
      menuNameInputRef.current?.focus();
    }
  }));

  return (
    <TextField inputRef={menuNameInputRef} label="메뉴명" />
  );
});

4. react-hook-form의 Controller와 함께 사용할 때

ControllerinputRef prop을 직접 받지 않음.
따라서 render 함수 내에서 field.ref와 직접 연결해야 함.

const inputRef = useRef<HTMLInputElement>(null);

<Controller
  name="menuName"
  control={control}
  render={({ field }) => (
    <TextField
      {...field}
      inputRef={(el) => {
        field.ref(el);         // RHF가 인풋을 제어할 수 있도록 연결
        inputRef.current = el; // 사용자 정의 ref 저장
      }}
    />
  )}
/>

5. 요약

  • forwardRef를 써야 컴포넌트에 ref 전달 가능
  • useImperativeHandle로 외부에 필요한 동작만 노출
  • Controller와 함께 쓸 땐 inputRef는 직접 연결해야 하며, field.ref와 동시에 바인딩해야 함

6. 왜 inputRef를 직접 쓰면 문제가 생기는가?

기본적으로는 ...field 안에 ref가 이미 포함되어 있음

<Controller
  name="menuName"
  control={control}
  render={({ field }) => (
    <TextField {...field} label="메뉴명" />
  )}
/>
  • 여기서 ...field 안에는 다음이 포함되어 있음:
    • value
    • onChange
    • onBlur
    • ref ← 이게 핵심!

따라서 inputRef를 따로 지정하지 않으면 MUI 내부에서 <input ref={...} />로 연결되며, RHF는 정상 작동함.


그런데 우리가 직접 inputRef={(el) => ...}를 지정하게 되면?

<TextField
  {...field}
  inputRef={(el) => {
    field.ref(el);          // 꼭 다시 연결해줘야 함!
    inputRef.current = el;  // 외부 제어용 저장
  }}
/>

이때는 MUI가 RHF의 ref를 더 이상 자동 연결해주지 않음.
➡ 우리가 수동으로 field.ref(el)을 꼭 호출해야 RHF가 제대로 작동함.


요약

사용 방식RHF 연결 여부
...field만 사용✅ 자동 연결
inputRef를 따로 지정❌ 수동 연결 필요 (반드시 field.ref(el) 호출)

이 차이를 모르면 reset(), watch(), 유효성 검사가 안 되는 이유를 알 수 없음.

profile
내일도 풀스택

0개의 댓글