Skip to content

Commit fed3ee3

Browse files
luffywuliaooliviertassinari
authored andcommitted
[Portal] Support any children node (#18692)
1 parent 0090249 commit fed3ee3

File tree

2 files changed

+88
-88
lines changed

2 files changed

+88
-88
lines changed

packages/material-ui/src/Portal/Portal.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect
2020
const Portal = React.forwardRef(function Portal(props, ref) {
2121
const { children, container, disablePortal = false, onRendered } = props;
2222
const [mountNode, setMountNode] = React.useState(null);
23-
const handleRef = useForkRef(children.ref, ref);
23+
const handleRef = useForkRef(React.isValidElement(children) ? children.ref : null, ref);
2424

2525
useEnhancedEffect(() => {
2626
if (!disablePortal) {
@@ -46,10 +46,12 @@ const Portal = React.forwardRef(function Portal(props, ref) {
4646
}, [onRendered, mountNode, disablePortal]);
4747

4848
if (disablePortal) {
49-
React.Children.only(children);
50-
return React.cloneElement(children, {
51-
ref: handleRef,
52-
});
49+
if (React.isValidElement(children)) {
50+
return React.cloneElement(children, {
51+
ref: handleRef,
52+
});
53+
}
54+
return children;
5355
}
5456

5557
return mountNode ? ReactDOM.createPortal(children, mountNode) : mountNode;
Lines changed: 81 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
/* eslint-disable react/prop-types */
22
import React from 'react';
3-
import { assert } from 'chai';
3+
import { expect } from 'chai';
44
import { spy } from 'sinon';
5-
import { createMount, createRender } from '@material-ui/core/test-utils';
5+
import { createRender } from '@material-ui/core/test-utils';
66
import consoleErrorMock from 'test/utils/consoleErrorMock';
7+
import { createClientRender } from 'test/utils/createClientRender';
78
import Portal from './Portal';
89

910
describe('<Portal />', () => {
10-
let mount;
11-
let render;
11+
let serverRender;
12+
const render = createClientRender({ strict: true });
1213

1314
before(() => {
14-
mount = createMount({ strict: true });
15-
render = createRender();
16-
});
17-
18-
after(() => {
19-
mount.cleanUp();
15+
serverRender = createRender();
2016
});
2117

2218
describe('server-side', () => {
@@ -34,64 +30,65 @@ describe('<Portal />', () => {
3430
});
3531

3632
it('render nothing on the server', () => {
37-
const markup1 = render(<div>Bar</div>);
38-
assert.strictEqual(markup1.text(), 'Bar');
33+
const markup1 = serverRender(<div>Bar</div>);
34+
expect(markup1.text()).to.equal('Bar');
3935

40-
const markup2 = render(
36+
const markup2 = serverRender(
4137
<Portal>
4238
<div>Bar</div>
4339
</Portal>,
4440
);
45-
assert.strictEqual(markup2.text(), '');
41+
expect(markup2.text()).to.equal('');
4642
});
4743
});
4844

4945
describe('ref', () => {
5046
it('should have access to the mountNode when disabledPortal={false}', () => {
5147
const refSpy = spy();
52-
const wrapper = mount(
48+
const { unmount } = render(
5349
<Portal ref={refSpy}>
5450
<h1>Foo</h1>
5551
</Portal>,
5652
);
57-
assert.deepEqual(refSpy.args, [[document.body]]);
58-
wrapper.unmount();
59-
assert.deepEqual(refSpy.args, [[document.body], [null]]);
53+
expect(refSpy.args).to.deep.equal([[document.body]]);
54+
unmount();
55+
expect(refSpy.args).to.deep.equal([[document.body], [null]]);
6056
});
6157

6258
it('should have access to the mountNode when disabledPortal={true}', () => {
6359
const refSpy = spy();
64-
const wrapper = mount(
60+
const { unmount } = render(
6561
<Portal disablePortal ref={refSpy}>
6662
<h1 className="woofPortal">Foo</h1>
6763
</Portal>,
6864
);
6965
const mountNode = document.querySelector('.woofPortal');
70-
assert.deepEqual(refSpy.args, [[mountNode]]);
71-
wrapper.unmount();
72-
assert.deepEqual(refSpy.args, [[mountNode], [null]]);
66+
expect(refSpy.args).to.deep.equal([[mountNode]]);
67+
unmount();
68+
expect(refSpy.args).to.deep.equal([[mountNode], [null]]);
7369
});
7470

7571
it('should have access to the mountNode when switching disabledPortal', () => {
7672
const refSpy = spy();
77-
const wrapper = mount(
73+
const { setProps, unmount } = render(
7874
<Portal disablePortal ref={refSpy}>
7975
<h1 className="woofPortal">Foo</h1>
8076
</Portal>,
8177
);
8278
const mountNode = document.querySelector('.woofPortal');
83-
assert.deepEqual(refSpy.args, [[mountNode]]);
84-
wrapper.setProps({
79+
expect(refSpy.args).to.deep.equal([[mountNode]]);
80+
setProps({
8581
disablePortal: false,
82+
ref: refSpy,
8683
});
87-
assert.deepEqual(refSpy.args, [[mountNode], [null], [document.body]]);
88-
wrapper.unmount();
89-
assert.deepEqual(refSpy.args, [[mountNode], [null], [document.body], [null]]);
84+
expect(refSpy.args).to.deep.equal([[mountNode], [null], [document.body]]);
85+
unmount();
86+
expect(refSpy.args).to.deep.equal([[mountNode], [null], [document.body], [null]]);
9087
});
9188
});
9289

9390
it('should render in a different node', () => {
94-
mount(
91+
render(
9592
<div id="test1">
9693
<h1 className="woofPortal1">Foo</h1>
9794
<Portal>
@@ -100,8 +97,8 @@ describe('<Portal />', () => {
10097
</div>,
10198
);
10299
const rootElement = document.querySelector('#test1');
103-
assert.strictEqual(rootElement.contains(document.querySelector('.woofPortal1')), true);
104-
assert.strictEqual(rootElement.contains(document.querySelector('.woofPortal2')), false);
100+
expect(rootElement.contains(document.querySelector('.woofPortal1'))).to.equal(true);
101+
expect(rootElement.contains(document.querySelector('.woofPortal2'))).to.equal(false);
105102
});
106103

107104
it('should unmount when parent unmounts', () => {
@@ -122,29 +119,30 @@ describe('<Portal />', () => {
122119
);
123120
}
124121

125-
const wrapper = mount(<Parent />);
126-
assert.strictEqual(document.querySelectorAll('#test1').length, 1);
127-
wrapper.setProps({ show: false });
128-
assert.strictEqual(document.querySelectorAll('#test1').length, 0);
122+
const { setProps } = render(<Parent />);
123+
expect(document.querySelectorAll('#test1').length).to.equal(1);
124+
setProps({ show: false });
125+
expect(document.querySelectorAll('#test1').length).to.equal(0);
129126
});
130127

131128
it('should render overlay into container (document)', () => {
132-
mount(
129+
render(
133130
<Portal>
134-
<div id="test2" />
131+
<div className="test2" />
132+
<div className="test2" />
135133
</Portal>,
136134
);
137-
assert.strictEqual(document.querySelectorAll('#test2').length, 1);
135+
expect(document.querySelectorAll('.test2').length).to.equal(2);
138136
});
139137

140138
it('should render overlay into container (DOMNode)', () => {
141139
const container = document.createElement('div');
142-
mount(
140+
render(
143141
<Portal container={container}>
144142
<div id="test2" />
145143
</Portal>,
146144
);
147-
assert.strictEqual(container.querySelectorAll('#test2').length, 1);
145+
expect(container.querySelectorAll('#test2').length).to.equal(1);
148146
});
149147

150148
it('should change container on prop change', () => {
@@ -165,82 +163,82 @@ describe('<Portal />', () => {
165163
);
166164
}
167165

168-
const wrapper = mount(<ContainerTest />);
169-
assert.strictEqual(document.querySelector('#test3').parentElement.nodeName, 'SPAN', 'c');
170-
wrapper.setProps({
166+
const { setProps } = render(<ContainerTest />);
167+
expect(document.querySelector('#test3').parentElement.nodeName).to.equal('SPAN');
168+
setProps({
171169
containerElement: true,
170+
disablePortal: true,
172171
});
173-
assert.strictEqual(document.querySelector('#test3').parentElement.nodeName, 'SPAN', 'a');
174-
wrapper.setProps({
172+
expect(document.querySelector('#test3').parentElement.nodeName).to.equal('SPAN');
173+
setProps({
174+
containerElement: true,
175175
disablePortal: false,
176176
});
177-
assert.strictEqual(document.querySelector('#test3').parentElement.nodeName, 'STRONG', 'c');
178-
wrapper.setProps({
177+
expect(document.querySelector('#test3').parentElement.nodeName).to.equal('STRONG');
178+
setProps({
179179
containerElement: false,
180+
disablePortal: false,
180181
});
181-
assert.strictEqual(document.querySelector('#test3').parentElement.nodeName, 'BODY', 'b');
182+
expect(document.querySelector('#test3').parentElement.nodeName).to.equal('BODY');
182183
});
183184

184185
it('should call onRendered', () => {
185186
const ref = React.createRef();
186187
const handleRendered = spy();
187-
mount(
188+
render(
188189
<Portal
189190
ref={ref}
190191
onRendered={() => {
191192
handleRendered();
192-
assert.strictEqual(ref.current !== null, true);
193+
expect(ref.current !== null).to.equal(true);
193194
}}
194195
>
195196
<div />
196197
</Portal>,
197198
);
198-
assert.strictEqual(handleRendered.callCount, 1);
199+
expect(handleRendered.callCount).to.equal(1);
199200
});
200201

201-
it('should call onRendered after child componentDidUpdate', () => {
202+
it('should call ref after child effect', () => {
203+
const callOrder = [];
204+
const handleRef = node => {
205+
if (node) {
206+
callOrder.push('ref');
207+
}
208+
};
209+
const updateFunction = () => {
210+
callOrder.push('effect');
211+
};
212+
202213
function Test(props) {
203-
const { updateFunction, container, onRendered } = props;
214+
const { container } = props;
204215

205216
React.useEffect(() => {
206217
updateFunction();
207-
}, [container, updateFunction]);
218+
}, [container]);
208219

209220
return (
210-
<Portal onRendered={onRendered} container={container}>
221+
<Portal ref={handleRef} container={container}>
211222
<div />
212223
</Portal>
213224
);
214225
}
215226

216-
const callOrder = [];
217-
const onRendered = () => {
218-
callOrder.push('onRendered');
219-
};
220-
const updateFunction = () => {
221-
callOrder.push('cDU');
222-
};
223-
224-
const container1 = document.createElement('div');
225-
const container2 = document.createElement('div');
226-
227-
const wrapper = mount(
228-
<Test onRendered={onRendered} updateFunction={updateFunction} container={container1} />,
229-
);
230-
231-
wrapper.setProps({ container: null });
232-
wrapper.setProps({ container: container2 });
233-
wrapper.setProps({ container: null });
234-
235-
assert.deepEqual(callOrder, [
236-
'cDU',
237-
'onRendered',
238-
'cDU',
239-
'onRendered',
240-
'cDU',
241-
'onRendered',
242-
'cDU',
243-
'onRendered',
227+
const { setProps } = render(<Test container={document.createElement('div')} />);
228+
229+
setProps({ container: null });
230+
setProps({ container: document.createElement('div') });
231+
setProps({ container: null });
232+
233+
expect(callOrder).to.deep.equal([
234+
'effect',
235+
'ref',
236+
'effect',
237+
'ref',
238+
'effect',
239+
'ref',
240+
'effect',
241+
'ref',
244242
]);
245243
});
246244
});

0 commit comments

Comments
 (0)