환경
- 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