본문 바로가기

react

[trouble-shooting]ref를 사용하는 컴포넌트에 라이브러리의 ref를 결합하기[useImperativeHandle]

문제상황

회사 프로젝트에서 사용할 공용 컴포넌트를 만들고 있었다.

checkbox 였는데, 내부적으로 ref를 만들고  input에 연결해서 dom 조작을 하는 기능을 넣어야 했다.

한편 프로젝트 내 여러 페이지에 form에 사용해야 해서 react-hook-form 라이브러리를 도입하였는데, 이 라이브러리는 register 혹은 Controller라는 훅을 사용하여서 해당 함수가 반환하는 onChange, ref, name 등을 input에다가 연결해줘야 했다.

form에서 checkbox컴포넌트를 쓰기 위해서는 라이브러리 훅의 ref를 컴포넌트 내부의 input에 연결해줘야 했는데, 이미 이 input에는 컴포넌트 내부의 ref가 할당되어 있었다.

 

컴포넌트 구조 상, 내부에서 할당한 ref를 교체할 수도 없었기 때문에 고민에 빠졌다.

 

해결방안

내 해결책은 이러했다.

컴포넌트 내부의 ref와 로직, 연결된 input은 놔두면서도, 컴포넌트 외부에서 주입된 ref는 컴포넌트 내부의 ref와 동기화시켜주는 것이다. 즉, 외부의 ref와 내부의 ref가 동일하게 input을 참조하도록하는 것이었다.

 

이를 위해 useImperativeHandle 훅을 사용하였다.

 

useImperativeHandle 

이 훅은 ref 객체에서 노출할 수 있는 내부 키:속성을 설정한다.

 

예컨대, 아래 코드에서는 부모 컴포넌트의 ref를 자식 컴포넌트의 dom에 바로 연결하는 대신,

부모의 ref => 자식의 ref => dom과 같은 구조로 연결하기 위해 ref를 사용한다.

이 때, 부모는 자식에게서 필요한 부분만 참조하게 만들 수 있다.

import { forwardRef, useRef, useImperativeHandle } from 'react';

// 자식 컴포넌트
const MyInput = forwardRef(function MyInput(props, ref) {
 // 내부 컴포넌트의 ref를 dom에 연결
 const inputRef = useRef(null);

// 부모 컴포넌트가 주입한 ref는 내부 컴포넌트의 ref를 통해 dom의 일부만 제공받음
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

 

이 방식을 사용해서 부모가 주입한 ref랑 컴포넌트 내부의 ref를 다음처럼 동기화할 수 있다.

* useImperativeHandle의 의존성은 해당 훅 내에서 prop나 state를 사용하는 경우 넣어주면 된다.

import { forwardRef, useRef, useImperativeHandle } from 'react';

// 자식 컴포넌트
const MyInput = forwardRef(function MyInput(props, ref) {
 // 내부 컴포넌트의 ref를 dom에 연결
 const inputRef = useRef(null);

// 부모 컴포넌트가 주입한 ref를 내부 컴포넌트의 ref에 동기화
useImperativeHandle(ref, () => internalRef.current);

  return <input {...props} ref={inputRef} />;
});

 

참고자료

https://ko.react.dev/reference/react/useImperativeHandle#usage

 

https://velog.io/@jay/useImperativeHandle-%EB%96%A0%EB%A8%B9%EC%97%AC%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4