Skip to content

Commit efba6a5

Browse files
authored
feat: add _internalComponents for antd to fix findDOMNode warning (#715)
* chore: update interface * test: update testcase
1 parent 81eafee commit efba6a5

File tree

5 files changed

+87
-39
lines changed

5 files changed

+87
-39
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"devDependencies": {
5454
"@rc-component/father-plugin": "^1.0.0",
5555
"@testing-library/jest-dom": "^6.1.5",
56-
"@testing-library/react": "^14.0.0",
56+
"@testing-library/react": "^15.0.7",
5757
"@types/jest": "^29.5.2",
5858
"@types/react": "^18.2.14",
5959
"@types/react-dom": "^18.2.6",
@@ -68,8 +68,8 @@
6868
"less": "^4.1.3",
6969
"np": "^9.0.0",
7070
"rc-test": "^7.0.14",
71-
"react": "^18.0.0",
72-
"react-dom": "^18.0.0",
71+
"react": "^18.3.1",
72+
"react-dom": "^18.3.1",
7373
"regenerator-runtime": "^0.14.0",
7474
"typescript": "^5.1.6"
7575
},

src/Menu.tsx

+28-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import useMemoCallback from './hooks/useMemoCallback';
2121
import useUUID from './hooks/useUUID';
2222
import type {
2323
BuiltinPlacements,
24+
Components,
2425
ItemType,
2526
MenuClickEventHandler,
2627
MenuInfo,
@@ -60,6 +61,7 @@ export interface MenuProps
6061
prefixCls?: string;
6162
rootClassName?: string;
6263
items?: ItemType[];
64+
6365
/** @deprecated Please use `items` instead */
6466
children?: React.ReactNode;
6567

@@ -148,6 +150,14 @@ export interface MenuProps
148150
disabled: boolean;
149151
},
150152
) => React.ReactElement;
153+
154+
/**
155+
* @private NEVER! EVER! USE IN PRODUCTION!!!
156+
* This is a hack API for `antd` to fix `findDOMNode` issue.
157+
* Not use it! Not accept any PR try to make it as normal API.
158+
* By zombieJ
159+
*/
160+
_internalComponents?: Components;
151161
}
152162

153163
interface LegacyMenuProps extends MenuProps {
@@ -228,12 +238,20 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
228238
_internalRenderMenuItem,
229239
_internalRenderSubMenuItem,
230240

241+
_internalComponents,
242+
231243
...restProps
232244
} = props as LegacyMenuProps;
233245

234-
const childList: React.ReactElement[] = React.useMemo(
235-
() => parseItems(children, items, EMPTY_LIST),
236-
[children, items],
246+
const [childList, measureChildList]: [
247+
visibleChildList: React.ReactElement[],
248+
measureChildList: React.ReactElement[],
249+
] = React.useMemo(
250+
() => [
251+
parseItems(children, items, EMPTY_LIST, _internalComponents),
252+
parseItems(children, items, EMPTY_LIST, {}),
253+
],
254+
[children, items, _internalComponents],
237255
);
238256

239257
const [mounted, setMounted] = React.useState(false);
@@ -274,9 +292,8 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
274292
};
275293

276294
// >>>>> Cache & Reset open keys when inlineCollapsed changed
277-
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] = React.useState(
278-
mergedOpenKeys,
279-
);
295+
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] =
296+
React.useState(mergedOpenKeys);
280297

281298
const mountRef = React.useRef(false);
282299

@@ -352,9 +369,10 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
352369
[registerPath, unregisterPath],
353370
);
354371

355-
const pathUserContext = React.useMemo(() => ({ isSubPathKey }), [
356-
isSubPathKey,
357-
]);
372+
const pathUserContext = React.useMemo(
373+
() => ({ isSubPathKey }),
374+
[isSubPathKey],
375+
);
358376

359377
React.useEffect(() => {
360378
refreshOverflowKeys(
@@ -653,7 +671,7 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
653671
{/* Measure menu keys. Add `display: none` to avoid some developer miss use the Menu */}
654672
<div style={{ display: 'none' }} aria-hidden>
655673
<PathRegisterContext.Provider value={registerPathContext}>
656-
{childList}
674+
{measureChildList}
657675
</PathRegisterContext.Provider>
658676
</div>
659677
</MenuContextProvider>

src/interface.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface ItemSharedProps {
88
}
99

1010
export interface SubMenuType extends ItemSharedProps {
11-
type?: "submenu";
11+
type?: 'submenu';
1212

1313
label?: React.ReactNode;
1414

@@ -133,3 +133,8 @@ export type MenuRef = {
133133
focus: (options?: FocusOptions) => void;
134134
list: HTMLUListElement;
135135
};
136+
137+
// ======================== Component ========================
138+
export type ComponentType = 'submenu' | 'item' | 'group' | 'divider';
139+
140+
export type Components = Partial<Record<ComponentType, React.ComponentType<any>>>;

src/utils/nodeUtil.tsx

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import * as React from 'react';
2-
import type { ItemType } from '../interface';
3-
import MenuItemGroup from '../MenuItemGroup';
4-
import SubMenu from '../SubMenu';
52
import Divider from '../Divider';
3+
import type { Components, ItemType } from '../interface';
64
import MenuItem from '../MenuItem';
5+
import MenuItemGroup from '../MenuItemGroup';
6+
import SubMenu from '../SubMenu';
77
import { parseChildren } from './commonUtil';
88

9+
function convertItemsToNodes(
10+
list: ItemType[],
11+
components: Required<Components>,
12+
) {
13+
const {
14+
item: MergedMenuItem,
15+
group: MergedMenuItemGroup,
16+
submenu: MergedSubMenu,
17+
divider: MergedDivider,
18+
} = components;
919

10-
function convertItemsToNodes(list: ItemType[]) {
1120
return (list || [])
1221
.map((opt, index) => {
1322
if (opt && typeof opt === 'object') {
@@ -19,29 +28,29 @@ function convertItemsToNodes(list: ItemType[]) {
1928
if (type === 'group') {
2029
// Group
2130
return (
22-
<MenuItemGroup key={mergedKey} {...restProps} title={label}>
23-
{convertItemsToNodes(children)}
24-
</MenuItemGroup>
31+
<MergedMenuItemGroup key={mergedKey} {...restProps} title={label}>
32+
{convertItemsToNodes(children, components)}
33+
</MergedMenuItemGroup>
2534
);
2635
}
2736

2837
// Sub Menu
2938
return (
30-
<SubMenu key={mergedKey} {...restProps} title={label}>
31-
{convertItemsToNodes(children)}
32-
</SubMenu>
39+
<MergedSubMenu key={mergedKey} {...restProps} title={label}>
40+
{convertItemsToNodes(children, components)}
41+
</MergedSubMenu>
3342
);
3443
}
3544

3645
// MenuItem & Divider
3746
if (type === 'divider') {
38-
return <Divider key={mergedKey} {...restProps} />;
47+
return <MergedDivider key={mergedKey} {...restProps} />;
3948
}
4049

4150
return (
42-
<MenuItem key={mergedKey} {...restProps}>
51+
<MergedMenuItem key={mergedKey} {...restProps}>
4352
{label}
44-
</MenuItem>
53+
</MergedMenuItem>
4554
);
4655
}
4756

@@ -54,11 +63,20 @@ export function parseItems(
5463
children: React.ReactNode | undefined,
5564
items: ItemType[] | undefined,
5665
keyPath: string[],
66+
components: Components,
5767
) {
5868
let childNodes = children;
5969

70+
const mergedComponents: Required<Components> = {
71+
divider: Divider,
72+
item: MenuItem,
73+
group: MenuItemGroup,
74+
submenu: SubMenu,
75+
...components,
76+
};
77+
6078
if (items) {
61-
childNodes = convertItemsToNodes(items);
79+
childNodes = convertItemsToNodes(items, mergedComponents);
6280
}
6381

6482
return parseChildren(childNodes, keyPath);

tests/Responsive.spec.tsx

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
/* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */
2-
import { fireEvent, render } from '@testing-library/react';
2+
import { act, fireEvent, render } from '@testing-library/react';
33
import ResizeObserver from 'rc-resize-observer';
44
import KeyCode from 'rc-util/lib/KeyCode';
5+
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
56
import React from 'react';
6-
import { act } from 'react-dom/test-utils';
77
import Menu, { MenuItem, SubMenu } from '../src';
88
import { OVERFLOW_KEY } from '../src/hooks/useKeyRecords';
99
import { last } from './util';
10-
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
1110

1211
jest.mock('rc-resize-observer', () => {
1312
const R = require('react');
@@ -29,7 +28,6 @@ jest.mock('rc-resize-observer', () => {
2928
});
3029
});
3130

32-
3331
describe('Menu.Responsive', () => {
3432
beforeEach(() => {
3533
global.resizeProps = null;
@@ -61,9 +59,18 @@ describe('Menu.Responsive', () => {
6159

6260
render(
6361
<React.StrictMode>
64-
<Menu mode="horizontal">
65-
<MyItem key="1">Good</MyItem>
66-
</Menu>
62+
<Menu
63+
mode="horizontal"
64+
_internalComponents={{
65+
item: MyItem,
66+
}}
67+
items={[
68+
{
69+
label: 'Good',
70+
key: '1',
71+
},
72+
]}
73+
/>
6774
</React.StrictMode>,
6875
);
6976

@@ -115,8 +122,8 @@ describe('Menu.Responsive', () => {
115122
get() {
116123
return () => ({
117124
width: 41,
118-
})
119-
}
125+
});
126+
},
120127
}));
121128
// Set container width
122129
act(() => {
@@ -129,8 +136,8 @@ describe('Menu.Responsive', () => {
129136
get() {
130137
return () => ({
131138
width: 20,
132-
})
133-
}
139+
});
140+
},
134141
}));
135142
// Resize every item
136143
getResizeProps()

0 commit comments

Comments
 (0)