Next.js组件测试中createPortal报错:Target container is not a DOM element
问题
在Next.js中测试组件时,渲染出现「Target container is not a DOM element」错误,相关代码及错误栈如下:
错误栈
Target container is not a DOM element. 16 | }, [selector]); 17 | > 18 | return mounted ? createPortal(children, document.querySelector(selector) as HTMLParagraphElement) : null; | ^ 19 | }; 20 | 21 | export default Portal;
Portal.tsx 代码
const Portal = ({ children, selector }: IProps) => { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); return () => setMounted(false); }, [selector]); return mounted ? createPortal(children, document.querySelector(selector) as HTMLParagraphElement) : null; };
目标测试组件代码
const PeriodSettingWrapper = () => { const [showModalState, setshowModalState] = useState(false); return ( <> <PeriodSetting setshowModalState={setshowModalState} /> <Portal selector="#portal"> <ModalTemplate height={50} visibleState={showModalState} setVisible={setshowModalState}> <DatePickerModal /> </ModalTemplate> </Portal> </> ); };
测试代码
describe('Period test', () => { test('must have to start date', () => { render( <RecoilRoot> <PeriodSettingWrapper /> </RecoilRoot> ); const text = screen.getByText('시작일'); expect(text).toBeInTheDocument(); }); });
解决方案
1. 测试环境提前创建目标DOM元素
测试用的JSDOM环境默认没有#portal元素,需要在测试前后手动管理该元素:
describe('Period test', () => { beforeEach(() => { // 创建portal容器 const portalDiv = document.createElement('div'); portalDiv.id = 'portal'; document.body.appendChild(portalDiv); }); afterEach(() => { // 测试后清理元素 const portalDiv = document.getElementById('portal'); if (portalDiv) document.body.removeChild(portalDiv); }); test('must have to start date', () => { render( <RecoilRoot> <PeriodSettingWrapper /> </RecoilRoot> ); const text = screen.getByText('시작일'); expect(text).toBeInTheDocument(); }); });
2. 增强Portal组件的容错性
添加DOM元素存在性检查,避免找不到元素时崩溃:
const Portal = ({ children, selector }: IProps) => { const [mounted, setMounted] = useState(false); const [targetElement, setTargetElement] = useState<HTMLElement | null>(null); useEffect(() => { const element = document.querySelector(selector); setTargetElement(element as HTMLElement); setMounted(true); return () => { setMounted(false); setTargetElement(null); }; }, [selector]); // 元素不存在时不渲染 if (!mounted || !targetElement) return null; return createPortal(children, targetElement); };
3. 标记Portal为客户端组件
Next.js中确保Portal只在客户端渲染,避免服务端渲染时的document不存在问题(测试环境也适用):
在Portal.tsx顶部添加客户端组件标记:
'use client';
内容的提问来源于stack exchange,提问作者김정수
相关产品推荐
相关产品推荐

