[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
}