Skip to content

Commit cba467a

Browse files
authored
chore: Should not re-render when dialog is invisible (#212)
* fix: Dialog should not update children when !visible * chore: test to tsx * test: fix test case
1 parent 99ebd4f commit cba467a

File tree

4 files changed

+59
-14
lines changed

4 files changed

+59
-14
lines changed

examples/bootstrap.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import '../assets/bootstrap.less';
33
import * as React from 'react';
44
import Dialog from '../src/DialogWrap';
55

6+
// Check for memo update should work
7+
const InnerRender = () => {
8+
console.log('Updated...', Date.now());
9+
return null;
10+
};
11+
612
const MyControl = () => {
713
const [visible, setVisible] = React.useState(false);
814
const [destroyOnClose, setDestroyOnClose] = React.useState(false);
@@ -37,6 +43,7 @@ const MyControl = () => {
3743
</button>,
3844
]}
3945
>
46+
<InnerRender />
4047
<h4>Text in a modal</h4>
4148
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
4249
<hr />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from 'react';
2+
3+
export interface MemoChildrenProps {
4+
visible: boolean;
5+
children: React.ReactNode;
6+
}
7+
8+
export default React.memo(
9+
({ children }: MemoChildrenProps) => children as React.ReactElement,
10+
(_, { visible }) => !visible,
11+
);

src/Dialog/Content.tsx renamed to src/Dialog/Content/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import * as React from 'react';
22
import { useRef } from 'react';
33
import classNames from 'classnames';
44
import CSSMotion from 'rc-motion';
5-
import { IDialogChildProps } from '.';
6-
import { offset } from '../util';
5+
import { IDialogChildProps } from '..';
6+
import { offset } from '../../util';
7+
import MemoChildren from './MemoChildren';
78

89
const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
910

@@ -147,7 +148,9 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
147148
onClick={onClick}
148149
>
149150
<div tabIndex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
150-
{modalRender ? modalRender(content) : content}
151+
<MemoChildren visible={visible}>
152+
{modalRender ? modalRender(content) : content}
153+
</MemoChildren>
151154
<div tabIndex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
152155
</div>
153156
)}

tests/index.spec.js renamed to tests/index.spec.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ describe('dialog', () => {
9090
jest.runAllTimers();
9191
wrapper.update();
9292

93-
document.getElementsByClassName('.test-input').value = 'test';
94-
expect(document.getElementsByClassName('.test-input').value).toBe('test');
93+
((document.getElementsByClassName('.test-input') as unknown) as HTMLInputElement).value =
94+
'test';
95+
expect(
96+
((document.getElementsByClassName('.test-input') as unknown) as HTMLInputElement).value,
97+
).toBe('test');
9598

9699
// Hide
97100
wrapper.setProps({ visible: false });
@@ -103,7 +106,9 @@ describe('dialog', () => {
103106
jest.runAllTimers();
104107
wrapper.update();
105108

106-
expect(document.getElementsByClassName('.test-input').value).toBeUndefined();
109+
expect(
110+
((document.getElementsByClassName('.test-input') as unknown) as HTMLInputElement).value,
111+
).toBeUndefined();
107112
wrapper.unmount();
108113
});
109114
});
@@ -201,7 +206,9 @@ describe('dialog', () => {
201206
describe('Tab should keep focus in dialog', () => {
202207
it('basic tabbing', () => {
203208
const wrapper = mount(<Dialog visible />, { attachTo: document.body });
204-
const sentinelEnd = document.querySelectorAll('.rc-dialog-content + div')[0];
209+
const sentinelEnd = (document.querySelectorAll(
210+
'.rc-dialog-content + div',
211+
)[0] as unknown) as HTMLDivElement;
205212
sentinelEnd.focus();
206213

207214
wrapper.find('.rc-dialog-wrap').simulate('keyDown', {
@@ -237,16 +244,13 @@ describe('dialog', () => {
237244
// Trigger position align
238245
act(() => {
239246
wrapper
240-
.find('Content CSSMotion')
247+
.find<any>('Content CSSMotion' as any)
241248
.props()
242249
.onAppearPrepare();
243250
});
244251

245252
expect(
246-
wrapper
247-
.find('.rc-dialog')
248-
.at(0)
249-
.getDOMNode().style['transform-origin'],
253+
(wrapper.find('.rc-dialog').at(0).getDOMNode() as HTMLDivElement).style['transform-origin'],
250254
).toBeTruthy();
251255
});
252256

@@ -318,7 +322,7 @@ describe('dialog', () => {
318322
}
319323

320324
const wrapper = mount(
321-
<DialogWrapTest visible forceRender>
325+
<DialogWrapTest>
322326
<div>Show dialog with forceRender and visible is true</div>
323327
</DialogWrapTest>,
324328
);
@@ -331,7 +335,7 @@ describe('dialog', () => {
331335
const modalRender = mount(
332336
<Dialog
333337
visible
334-
modalRender={node =>
338+
modalRender={(node: React.ReactElement) =>
335339
cloneElement(node, { ...node.props, style: { background: '#1890ff' } })
336340
}
337341
/>,
@@ -350,4 +354,24 @@ describe('dialog', () => {
350354
expect(wrapper.find('.rc-dialog').props().style.height).toEqual(903);
351355
});
352356
});
357+
358+
it('should not re-render when visible changed', () => {
359+
let renderTimes = 0;
360+
const RenderChecker = () => {
361+
renderTimes += 1;
362+
return null;
363+
};
364+
365+
const wrapper = mount(
366+
<Dialog visible>
367+
<RenderChecker />
368+
</Dialog>,
369+
);
370+
371+
expect(renderTimes).toEqual(1);
372+
373+
// Hidden should not trigger render
374+
wrapper.setProps({ visible: false });
375+
expect(renderTimes).toEqual(1);
376+
});
353377
});

0 commit comments

Comments
 (0)