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

如何在Portal封装组件中合规条件调用React useRef并优化代码?

Optimizing Your React Portal Component for Hook Rule Compliance

Great work building a Portal component that checks all your requirement boxes—handling multiple instances, targeting specific parent elements, and cleaning up properly is no small win. You’re totally right to flag the hook rule violation in your current code: even if you don’t see warnings now, calling useRef inside a ternary breaks React’s Rules of Hooks, which depend on consistent hook call order across renders. This could lead to subtle, hard-to-debug bugs later on, especially in more complex component trees or different build environments.

Let’s refactor your component to fix this issue while keeping all your desired functionality intact:

Refactored Portal Component

import * as React from 'react';
import { createPortal } from 'react-dom';

export interface PortalProps {
  children: React.ReactElement;
  parentId?: string;
}

export const Portal = ({ children, parentId }: PortalProps): React.ReactElement | null => {
  // Declare ref at the component top level (fully compliant with hook rules)
  const containerRef = React.useRef<HTMLElement | null>(null);

  React.useEffect(() => {
    // Clean up any auto-created container from previous renders
    if (containerRef.current && !parentId) {
      document.body.removeChild(containerRef.current);
    }

    // Set up the correct container based on parentId
    if (parentId) {
      // Target an existing DOM element by ID
      containerRef.current = document.getElementById(parentId);
    } else {
      // Create a new div and attach it to the document body
      const newContainer = document.createElement('div');
      containerRef.current = newContainer;
      document.body.appendChild(newContainer);
    }

    // Cleanup function: runs on component unmount or when parentId changes
    return () => {
      // Only remove auto-created containers (never delete user-provided parent elements)
      if (containerRef.current && !parentId) {
        document.body.removeChild(containerRef.current);
      }
      containerRef.current = null;
    };
  }, [parentId]); // Re-run the effect whenever parentId changes

  // Guard clause: avoid rendering the portal if the container isn't ready
  if (!containerRef.current) {
    return null;
  }

  return createPortal(children, containerRef.current);
};

export default Portal;

Key Improvements

  • Hook Rule Compliance: All hooks are called at the component’s top level, so React can reliably track the ref state across renders.
  • Robust Cleanup: The cleanup function handles both component unmount and parentId changes—if you switch between targeting a specific parent and using an auto-created container, we’ll clean up the old container before setting up the new one.
  • Safety Checks: Added a guard clause to prevent passing null to createPortal, which avoids potential errors if the target element doesn’t exist yet.
  • Unchanged Functionality: All your original use cases work exactly as before:
    • Multiple Portal instances coexist without interference (each has its own independent containerRef).
    • Targeting a parent via parentId works without needing to pass ref props.
    • Omitting parentId auto-creates a container in the document body.

Quick Additional Tip

If your target parent element might be dynamically added to the DOM after the Portal mounts, you could extend this component with a MutationObserver to watch for the element’s existence. But for most static or pre-existing parent elements, the refactored code above will serve you perfectly.

内容的提问来源于stack exchange,提问作者Aaron Balthaser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 10:17:32