Next.js使用createPortal报错Target container is not a DOM element如何解决
报错原因
这个错误是Next.js服务端渲染(SSR)的特性导致的:
- 组件首次在服务端渲染时,不存在
document全局对象,此时你定义的root变量初始值为null - 即使你用
mount状态标记客户端挂载状态,但首次渲染时mount为false,root还是null,直接调用ReactDOM.createPortal时第二个参数为null就会触发目标容器不是DOM元素的错误
解决方案
修改组件的返回逻辑,仅在客户端挂载完成、成功获取到目标DOM节点后再返回Portal内容,否则返回空即可。
修改后的核心代码如下:
const HoveringToolbar = () => { const ref = useRef(); const editor = useSlate(); const [mount, setMount] = useState(false); useEffect(() => { setMount(true); return () => setMount(false); }, []); useEffect(() => { // 原有计算悬浮菜单位置的逻辑不变 const el = ref.current; const { selection } = editor; if (!el) { return; } if ( !selection || !ReactEditor.isFocused(editor) || Range.isCollapsed(selection) || Editor.string(editor, selection) === "" ) { el.removeAttribute("style"); return; } const domSelection = window.getSelection(); const domRange = domSelection.getRangeAt(0); const rect = domRange.getBoundingClientRect(); el.style.opacity = "1"; el.style.top = `${rect.top + window.pageYOffset - el.offsetHeight}px`; el.style.left = `${ rect.left + window.pageXOffset + 150 - el.offsetWidth / 2 + rect.width / 2 }px`; }); // 仅在客户端挂载完成后才获取DOM、返回Portal if (!mount) return null; const root = document.getElementById("__next"); // 兜底判断,如果__next节点不存在也返回空,避免报错 if (!root) return null; return ReactDOM.createPortal( <Portal> {/* 原有Menu和内部按钮逻辑不变 */} <Menu ref={ref} className={css` padding: 8px 7px 6px; position: absolute; height: 60px; margin-top: -6px; background-color: rgba(17, 105, 84, 0.94) !important; border-radius: 4px; transition: opacity 0.75s; display: flex; justify-content: center; align-items: center; box-sizing: border-box; z-index: 999; `}> <FormatButton format='bold' icon='FormatBoldIcon' /> <FormatButton format='italic' icon='FormatItalicIcon' /> <FormatButton format='underline' icon='FormatUnderlinedIcon' /> <BlockButton format='h1' icon='TextFieldsIcon' /> <BlockButton format='h2' icon='FormatSizeIcon' /> <BlockButton format='block-quote' icon='FormatQuoteIcon' /> <LinkButton /> <RemoveLinkButton /> </Menu> </Portal>, root, ); };
额外优化建议
如果没有特殊的挂载节点要求,也可以直接将Portal挂载到document.body,不需要依赖__next节点,兼容性更好。
内容的提问来源于stack exchange,提问作者Md Rezaul Karim
相关产品推荐
相关产品推荐

