diff --git a/.changeset/perf-action-menu-memo-context.md b/.changeset/perf-action-menu-memo-context.md new file mode 100644 index 00000000000..1e49bf75e99 --- /dev/null +++ b/.changeset/perf-action-menu-memo-context.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Memoize ActionMenu context values to prevent unnecessary re-renders of menu items diff --git a/packages/react/src/ActionMenu/ActionMenu.tsx b/packages/react/src/ActionMenu/ActionMenu.tsx index e501b117377..cbaf0015479 100644 --- a/packages/react/src/ActionMenu/ActionMenu.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.tsx @@ -164,22 +164,22 @@ const Menu: FCWithSlotMarker> = ({ } }) - return ( - - {contents} - + const isSubmenu = parentMenuContext.isSubmenu !== undefined + + const menuContextValue = useMemo( + () => ({ + anchorRef, + renderAnchor, + anchorId, + open: combinedOpenState, + onOpen, + onClose, + isSubmenu, + }), + [anchorRef, renderAnchor, anchorId, combinedOpenState, onOpen, onClose, isSubmenu], ) + + return {contents} } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -320,6 +320,20 @@ const Overlay: FCWithSlotMarker> = ({ } }, [anchorRef]) + const afterSelect = useCallback(() => onClose?.('item-select'), [onClose]) + + const overlayContextValue = useMemo( + () => ({ + container: 'ActionMenu' as const, + listRole: 'menu' as const, + listLabelledBy: ariaLabelledby || anchorAriaLabelledby || anchorId, + selectionAttribute: 'aria-checked' as const, + afterSelect, + enableFocusZone: isNarrowFullscreen, + }), + [ariaLabelledby, anchorAriaLabelledby, anchorId, afterSelect, isNarrowFullscreen], + ) + const featureFlagDisplayInViewportInsideDialog = useFeatureFlag( 'primer_react_action_menu_display_in_viewport_inside_dialog', ) @@ -351,17 +365,7 @@ const Overlay: FCWithSlotMarker> = ({ {...(overlayProps.overflow ? {[`data-overflow-${overlayProps.overflow}`]: ''} : {})} {...(overlayProps.maxHeight ? {[`data-max-height-${overlayProps.maxHeight}`]: ''} : {})} > - onClose?.('item-select'), - enableFocusZone: isNarrowFullscreen, // AnchoredOverlay takes care of focus zone. We only want to enable this if menu is narrow fullscreen. - }} - > + {children}