Skip to content

Commit d0e738b

Browse files
authored
fix:Not warning if content do not need key (#397)
* test: test driven * fix: Not warning of useless component
1 parent 2346e17 commit d0e738b

File tree

4 files changed

+111
-37
lines changed

4 files changed

+111
-37
lines changed

src/MenuItem.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export interface MenuItemProps
2828
/** @private Internal filled key. Do not set it directly */
2929
eventKey?: string;
3030

31+
/** @private Do not use. Private warning empty usage */
32+
warnKey?: boolean;
33+
3134
disabled?: boolean;
3235

3336
itemIcon?: RenderIconType;
@@ -120,6 +123,11 @@ const InternalMenuItem = (props: MenuItemProps) => {
120123

121124
const connectedKeys = useFullPath(eventKey);
122125

126+
// ================================ Warn ================================
127+
if (process.env.NODE_ENV !== 'production' && props.warnKey) {
128+
warning(false, 'MenuItem should not leave undefined `key`.');
129+
}
130+
123131
// ============================= Info =============================
124132
const getEventInfo = (
125133
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,

src/SubMenu/index.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import classNames from 'classnames';
33
import Overflow from 'rc-overflow';
4+
import warning from 'rc-util/lib/warning';
45
import SubMenuList from './SubMenuList';
56
import { parseChildren } from '../utils/nodeUtil';
67
import type {
@@ -40,6 +41,9 @@ export interface SubMenuProps {
4041
/** @private Internal filled key. Do not set it directly */
4142
eventKey?: string;
4243

44+
/** @private Do not use. Private warning empty usage */
45+
warnKey?: boolean;
46+
4347
// >>>>> Icon
4448
itemIcon?: RenderIconType;
4549
expandIcon?: RenderIconType;
@@ -130,6 +134,11 @@ const InternalSubMenu = (props: SubMenuProps) => {
130134
const elementRef = React.useRef<HTMLDivElement>();
131135
const popupRef = React.useRef<HTMLUListElement>();
132136

137+
// ================================ Warn ================================
138+
if (process.env.NODE_ENV !== 'production' && props.warnKey) {
139+
warning(false, 'SubMenu should not leave undefined `key`.');
140+
}
141+
133142
// ================================ Icon ================================
134143
const mergedItemIcon = itemIcon || contextItemIcon;
135144
const mergedExpandIcon = expandIcon || contextExpandIcon;
@@ -158,23 +167,25 @@ const InternalSubMenu = (props: SubMenuProps) => {
158167
}
159168
};
160169

161-
const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> = domEvent => {
162-
triggerChildrenActive(true);
163-
164-
onMouseEnter?.({
165-
key: eventKey,
166-
domEvent,
167-
});
168-
};
169-
170-
const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> = domEvent => {
171-
triggerChildrenActive(false);
172-
173-
onMouseLeave?.({
174-
key: eventKey,
175-
domEvent,
176-
});
177-
};
170+
const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> =
171+
domEvent => {
172+
triggerChildrenActive(true);
173+
174+
onMouseEnter?.({
175+
key: eventKey,
176+
domEvent,
177+
});
178+
};
179+
180+
const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> =
181+
domEvent => {
182+
triggerChildrenActive(false);
183+
184+
onMouseLeave?.({
185+
key: eventKey,
186+
domEvent,
187+
});
188+
};
178189

179190
const mergedActive = React.useMemo(() => {
180191
if (active) {

src/utils/nodeUtil.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import * as React from 'react';
2-
import warning from 'rc-util/lib/warning';
32
import toArray from 'rc-util/lib/Children/toArray';
43

54
export function parseChildren(children: React.ReactNode, keyPath: string[]) {
65
return toArray(children).map((child, index) => {
76
if (React.isValidElement(child)) {
8-
let { key } = child;
9-
if (key === null || key === undefined) {
10-
key = `tmp_key-${[...keyPath, index].join('-')}`;
7+
const { key } = child;
8+
let eventKey = (child.props as any)?.eventKey ?? key;
119

12-
if (process.env.NODE_ENV !== 'production') {
13-
warning(
14-
false,
15-
'MenuItem or SubMenu should not leave undefined `key`.',
16-
);
17-
}
10+
const emptyKey = eventKey === null || eventKey === undefined;
11+
12+
if (emptyKey) {
13+
eventKey = `tmp_key-${[...keyPath, index].join('-')}`;
14+
}
15+
16+
const cloneProps = {
17+
key: eventKey,
18+
eventKey,
19+
} as any;
20+
21+
if (process.env.NODE_ENV !== 'production' && emptyKey) {
22+
cloneProps.warnKey = true;
1823
}
1924

20-
return React.cloneElement(child, {
21-
key,
22-
eventKey: key,
23-
} as any);
25+
return React.cloneElement(child, cloneProps);
2426
}
2527

2628
return child;

tests/SubMenu.spec.js

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import { mount } from 'enzyme';
44
import { act } from 'react-dom/test-utils';
55
import Menu, { MenuItem, SubMenu } from '../src';
6+
import { resetWarned } from 'rc-util/lib/warning';
67

78
describe('SubMenu', () => {
89
beforeEach(() => {
@@ -160,8 +161,6 @@ describe('SubMenu', () => {
160161
});
161162

162163
it('fires openChange event', () => {
163-
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
164-
165164
const handleOpenChange = jest.fn();
166165
const wrapper = mount(
167166
<Menu onOpenChange={handleOpenChange}>
@@ -193,12 +192,66 @@ describe('SubMenu', () => {
193192
'tmp_key-1',
194193
'tmp_key-tmp_key-1-1',
195194
]);
195+
});
196196

197-
expect(errorSpy).toHaveBeenCalledWith(
198-
'Warning: MenuItem or SubMenu should not leave undefined `key`.',
199-
);
197+
describe('undefined key', () => {
198+
it('warning item', () => {
199+
resetWarned();
200+
201+
const errorSpy = jest
202+
.spyOn(console, 'error')
203+
.mockImplementation(() => {});
204+
205+
mount(
206+
<Menu>
207+
<MenuItem>1</MenuItem>
208+
</Menu>,
209+
);
200210

201-
errorSpy.mockRestore();
211+
expect(errorSpy).toHaveBeenCalledWith(
212+
'Warning: MenuItem should not leave undefined `key`.',
213+
);
214+
215+
errorSpy.mockRestore();
216+
});
217+
218+
it('warning sub menu', () => {
219+
resetWarned();
220+
221+
const errorSpy = jest
222+
.spyOn(console, 'error')
223+
.mockImplementation(() => {});
224+
225+
mount(
226+
<Menu>
227+
<SubMenu />
228+
</Menu>,
229+
);
230+
231+
expect(errorSpy).toHaveBeenCalledWith(
232+
'Warning: SubMenu should not leave undefined `key`.',
233+
);
234+
235+
errorSpy.mockRestore();
236+
});
237+
238+
it('should not warning', () => {
239+
resetWarned();
240+
241+
const errorSpy = jest
242+
.spyOn(console, 'error')
243+
.mockImplementation(() => {});
244+
245+
mount(
246+
<Menu>
247+
<Menu.Divider />
248+
</Menu>,
249+
);
250+
251+
expect(errorSpy).not.toHaveBeenCalled();
252+
253+
errorSpy.mockRestore();
254+
});
202255
});
203256

204257
describe('mouse events', () => {

0 commit comments

Comments
 (0)