[react] html 엘리먼트 (real dom element) 에 리액트 컴포넌트 (react component) 삽입(insert)하기

By | 1월 30, 2023

환경

  • react ^18.2.0
import React, { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import TestExt from "./TestExt";

/**
 * TestExt 컴포넌트를 특정 DOM 노드에 선택적으로 삽입하기 위해 wrapping한 컴포넌트
 */
const TestExtWrap = ({ isOpen }: { isOpen: boolean }) => {
  const wrap = document.getElementById("div-sample")!;
  return <>{isOpen && createPortal(<TestExt />, wrap)}</>;
};

/**
 * FC
 */
const Test = () => {

  const [isOpen, setOpen] = useState<boolean>(false);

  return (
    <>

      ...

      <TestExtWrap isOpen={isOpen} />
    </>
  );
};

export default Test;


참고











이하는 createPortal()을 몰랐을 때 작성했던 코드. 작동은 했었지만, 아래와 같은 여러가지 javascript warning을 만들어냈었다.

  • Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it

  • Warning: Attempted to synchronously unmount a root while React was already rendering. React cannot finish unmounting the root until the current render has completed, which may lead to a race condition.

  • Uncaught Error: Cannot update an unmounted root

시행착오를 기록하는 의미로 남겨놓음


/**
 * 테스트 react 컴포넌트
 */
const TestComp = ({ count, setCount }: { count: number; setCount: Dispatch<SetStateAction<number>> }) => {
  return (
    <>
      <span>테스트컴포넌트 [{count}]</span>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </>
  );
}; 

/**
 * FC
 */
const ParentComp = () => {
  const subWrapRef = useRef<any>(null); // react 컴포넌트를 삽입할 부모 엘리먼트 (직접 ref binding을 할 수 없는 상태 (예: 라이브러리가 generation하는 DOM) 가정)
  const [count, setCount] = useState<number>(0); // 상위 컴포넌트와의 상호작용 테스트용

  // 부모 엘리먼트를 ref에 바인딩
  const initRef = useRef<boolean>(false); // react strict mode 로 인한 warning 때문에 추가 (2회 실행 방지)
  useEffect(() => {
    if (!initRef.current) {
      const wrap = document.getElementById('testId')!; // react 컴포넌트를 삽입할 부모 엘리먼트 찾기
      subWrapRef.current = ReactDOM.createRoot(wrap); // react 18부터의 방법
      initRef.current = true;
    }
  }, []); // TODO: 지금은 deps가 비어 있지만, 부모 엘리먼트가 늦게 생성된다면 생성되는 시점에 관한 조건을 추가해 준다.

  // 부모 엘리먼트 내에 외부 react 컴포넌트 render
  useEffect(() => {
    subWrapRef.current.render(<TestComp count={count} setCount={setCount} />);
  }, [count]);

  ...

}  


참고

  • 서드파티 라이브러리가 생성하는 특정 엘리먼트(id=testId) 내에 react 컴포넌트를 삽입하는 상황을 가정함.
  • ReactDOM.render 가 18 버전부터 deprecated 선언되어, createRoot 사용
  • 위의 subWrapRef 를 ref화 하지 않을 경우 console warning 발생
  • 이 방식에 어떤 잠재적인 문제가 있는지는 검증되지 않음.
  • https://reactjs.org/docs/react-dom-client.html
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments