@samitouri / QOS-React-2 / commits / 4610359651

[DevTools] Fix null ref crash in ContextMenu when items list is empty (#35929)

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>

fre$h committed Mar 5, 2026 at 15:52 UTC 4610359651fa10247159e2050f8ec222cb7faa91
2 files changed +18 -9
packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js
+18 -8
@@ -8,7 +8,7 @@
8 */
9
10 import * as React from 'react';
11 -import {useLayoutEffect, createRef} from 'react';
11 +import {useLayoutEffect} from 'react';
12 import {createPortal} from 'react-dom';
13
14 import ContextMenuItem from './ContextMenuItem';
@@ -16,7 +16,6 @@ import ContextMenuItem from './ContextMenuItem';
16 import type {
17 ContextMenuItem as ContextMenuItemType,
18 ContextMenuPosition,
19 - ContextMenuRef,
19 } from './types';
20
21 import styles from './ContextMenu.css';
@@ -49,7 +48,6 @@ type Props = {
48 items: ContextMenuItemType[],
49 position: ContextMenuPosition,
50 hide: () => void,
52 - ref?: ContextMenuRef,
51 };
52
53 export default function ContextMenu({
@@ -57,7 +55,6 @@ export default function ContextMenu({
55 position,
56 items,
57 hide,
60 - ref = createRef(),
58 }: Props): React.Node {
59 // This works on the assumption that ContextMenu component is only rendered when it should be shown
60 const anchor = anchorElementRef.current;
@@ -73,8 +70,21 @@ export default function ContextMenu({
70 '[data-react-devtools-portal-root]',
71 );
72
73 + const hideMenu = portalContainer == null || items.length === 0;
74 + const menuRef = React.useRef<HTMLDivElement | null>(null);
75 +
76 useLayoutEffect(() => {
77 - const menu = ((ref.current: any): HTMLElement);
77 + // Match the early-return condition below.
78 + if (hideMenu) {
79 + return;
80 + }
81 + const maybeMenu = menuRef.current;
82 + if (maybeMenu === null) {
83 + throw new Error(
84 + "Can't access context menu element. This is a bug in React DevTools.",
85 + );
86 + }
87 + const menu = (maybeMenu: HTMLDivElement);
88
89 function hideUnlessContains(event: Event) {
90 if (!menu.contains(((event.target: any): Node))) {
@@ -98,14 +108,14 @@ export default function ContextMenu({
108
109 ownerWindow.removeEventListener('resize', hide);
110 };
101 - }, []);
111 + }, [hideMenu]);
112
103 - if (portalContainer == null || items.length === 0) {
113 + if (hideMenu) {
114 return null;
115 }
116
117 return createPortal(
108 - <div className={styles.ContextMenu} ref={ref}>
118 + <div className={styles.ContextMenu} ref={menuRef}>
119 {items.map(({onClick, content}, index) => (
120 <ContextMenuItem key={index} onClick={onClick} hide={hide}>
121 {content}
packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenuContainer.js
-1
@@ -53,7 +53,6 @@ export default function ContextMenuContainer({
53 position={position}
54 hide={hide}
55 items={items}
56 - ref={ref}
56 />
57 );
58 }