From cca0ed520e9c1201aea9e7ec4bcff83f061901c3 Mon Sep 17 00:00:00 2001 From: dogle <15707274049@163.com> Date: Sat, 5 Sep 2026 23:42:20 +0800 Subject: [PATCH 1/2] fix: update parent selection when menu items load dynamically --- src/hooks/useKeyRecords.ts | 16 +++++++----- tests/DynamicSelection.spec.tsx | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 tests/DynamicSelection.spec.tsx diff --git a/src/hooks/useKeyRecords.ts b/src/hooks/useKeyRecords.ts index d9af87d7..638b7919 100644 --- a/src/hooks/useKeyRecords.ts +++ b/src/hooks/useKeyRecords.ts @@ -11,7 +11,7 @@ const getPathKeys = (keyPathStr: string) => keyPathStr.split(PATH_SPLIT); export const OVERFLOW_KEY = 'rc-menu-more'; export default function useKeyRecords() { - const [, internalForceUpdate] = React.useState({}); + const [keyPathVersion, internalForceUpdate] = React.useState({}); const key2pathRef = useRef(new Map()); const path2keyRef = useRef(new Map()); const [overflowKeys, setOverflowKeys] = React.useState([]); @@ -80,7 +80,9 @@ export default function useKeyRecords() { const pathKeyList = getKeyPath(pathKey, true); return pathKeyList.includes(eventKey); }), - [getKeyPath], + // Path records live in refs, so updates must also refresh context consumers. + // eslint-disable-next-line react-hooks/exhaustive-deps + [getKeyPath, keyPathVersion], ); const getKeys = () => { const keys = [...key2pathRef.current.keys()]; @@ -107,12 +109,12 @@ export default function useKeyRecords() { return pathKeys; }, []); - React.useEffect( - () => () => { + React.useEffect(() => { + destroyRef.current = false; + return () => { destroyRef.current = true; - }, - [], - ); + }; + }, []); return { // Register diff --git a/tests/DynamicSelection.spec.tsx b/tests/DynamicSelection.spec.tsx new file mode 100644 index 00000000..489dc948 --- /dev/null +++ b/tests/DynamicSelection.spec.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; +import Menu from '../src'; +import type { MenuProps } from '../src'; + +describe.each([false, true])('dynamic selection with StrictMode=%s', strictMode => { + const wrapper = strictMode ? React.StrictMode : React.Fragment; + + describe.each(['defaultSelectedKeys', 'selectedKeys'] as const)('%s', selectionProp => { + it.each(['inline', 'vertical', 'horizontal'])( + 'highlights the parent when items load in %s mode', + async mode => { + const selectionProps = { [selectionProp]: ['child'] }; + const { container, rerender } = render( + , + { wrapper }, + ); + + await act(async () => { + rerender( + , + ); + }); + + expect(container.querySelector('.rc-menu-submenu')).toHaveClass('rc-menu-submenu-selected'); + }, + ); + }); +}); From 399750266c3d71733a08ae530e67e9ee00d7027a Mon Sep 17 00:00:00 2001 From: dogle <15707274049@163.com> Date: Sun, 6 Sep 2026 00:53:41 +0800 Subject: [PATCH 2/2] test: assert child selection after menu items load dynamically --- tests/DynamicSelection.spec.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/DynamicSelection.spec.tsx b/tests/DynamicSelection.spec.tsx index 489dc948..70492657 100644 --- a/tests/DynamicSelection.spec.tsx +++ b/tests/DynamicSelection.spec.tsx @@ -8,11 +8,11 @@ describe.each([false, true])('dynamic selection with StrictMode=%s', strictMode describe.each(['defaultSelectedKeys', 'selectedKeys'] as const)('%s', selectionProp => { it.each(['inline', 'vertical', 'horizontal'])( - 'highlights the parent when items load in %s mode', + 'highlights the parent and child when items load in %s mode', async mode => { const selectionProps = { [selectionProp]: ['child'] }; - const { container, rerender } = render( - , + const { container, getAllByText, rerender } = render( + , { wrapper }, ); @@ -21,6 +21,7 @@ describe.each([false, true])('dynamic selection with StrictMode=%s', strictMode { + expect(child.closest('.rc-menu-item')).toHaveClass('rc-menu-item-selected'); + }); }, ); });