Portal을 사용할때 단순히 아래와같이 사용하면 Target container is not a DOM element. 이라는 에러가 나타나는 경우가 있다.
const getButton = () => {
const closeButton = (
<button type="button" onClick={onCancel}>
닫기
</button>
);
return ReactDOM.createPortal(
closeButton,
document.getElementById("close-button")
);
};
return (
<>
// ...
{getButton()}
</>
);
이는 DOM에 렌더링 되기전 Portal로 컴포넌트를 이동시키려고 할때 나타나는 에러인데, public/index.html에서 작성한 dom 태그들은 무사히 컴포넌트가 Portal로 이동되지만 만약 자신보다 하위에 위치한 컴포넌트 안의 dom에 렌더링 하려할땐 위와같은 에러가 발생했다.
첫번째 시도
이동하려는 DOM이 무사히 렌더링 되었는지 확인해주면 된다. 그러나 이때 같은 컴포넌트 내에서 이동을 시키려고 하면 쉽게 ref를 확인할 수 있지만, 자신보다 하위에 위치한 컴포넌트를 렌더링 하려고 할때에는 ref를 얻어오기 곤란해진다. 그러므로 위에 렌더링 확인용 div를 추가해주면 ref를 확인해줄 수 있다.
const renderRef = useRef(null);
const getButton = () => {
// current로 DOM이 렌더링 되었는지 체크하기
if (!renderRef.current) return null;
const closeButton = (
<button type="button" onClick={onCancel}>
닫기
</button>
);
return ReactDOM.createPortal(
closeButton,
document.getElementById("close-button")
);
};
return (
<>
{/* 렌더링 확인용 상위 div 태그 추가 */}
<div id="render"></div>
{getButton()}
</>
);
그러나 이 방법의 문제점은 첫번째 렌더링에선 current값이 null로 찍혀 컴포넌트가 이동되지 않는 문제가 있었다. useRef의 특성상 첫번째 렌더링 이후에는 다시 렌더링 되지 않는 문제로 일어나는 버그였다.
해결방법
콜백 ref를 사용해주면 쉽게 해결할 수 있다. 콜백으로 받아온 값을 setState에 저장해준 후 확인해보면 값이 잘 업데이트 되는 모습을 볼 수 있다.
// 콜백 ref로 받아올 값을 저장할 useState
const [render, setRender] = useState(null);
const getButton = () => {
if (!render) return null;
const closeButton = (
<button type="button" onClick={onCancel}>
닫기
</button>
);
return ReactDOM.createPortal(
closeButton,
document.getElementById("close-button")
);
};
return (
<>
{/* 콜백 ref 사용하기 */}
<div
className="render"
ref={(el) => {
setRender(el);
}}
></div>
{getButton()}
</>
);
감사합니다.