You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者김정수

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 21:50:22