Make ReactDOM.createPortal() official (#10675)
* Validate portal container early * Add ReactDOM.createPortal but leave unstable_ alias usable
Dan Abramov committed
Sep 11, 2017 at 22:23 UTC
89508f20d49cde40d1083f7501fac388b86bbcb8
7 files changed
+67
-39
src/renderers/__tests__/ReactUpdates-test.js
+1
-4
@@ -522,10 +522,7 @@ describe('ReactUpdates', () => {
522
var portal = null;
523
// If we're using Fiber, we use Portals instead to achieve this.
524
if (ReactDOMFeatureFlags.useFiber) {
525
- portal = ReactDOM.unstable_createPortal(
526
- <B ref={n => (b = n)} />,
527
- bContainer,
528
- );
525
+ portal = ReactDOM.createPortal(<B ref={n => (b = n)} />, bContainer);
526
}
527
return <div>A{this.state.x}{portal}</div>;
528
}
src/renderers/dom/__tests__/ReactDOMProduction-test.js
+1
-4
@@ -246,10 +246,7 @@ describe('ReactDOMProduction', () => {
246
var expectSVG = {ref: el => svgEls.push(el)};
247
var expectHTML = {ref: el => htmlEls.push(el)};
248
var usePortal = function(tree) {
249
- return ReactDOM.unstable_createPortal(
250
- tree,
251
- document.createElement('div'),
252
- );
249
+ return ReactDOM.createPortal(tree, document.createElement('div'));
250
};
251
var assertNamespacesMatch = function(tree) {
252
var container = document.createElement('div');
src/renderers/dom/fiber/ReactDOMFiberEntry.js
+18
-8
@@ -646,7 +646,22 @@ function renderSubtreeIntoContainer(
646
return DOMRenderer.getPublicRootInstance(root);
647
}
648
649
+function createPortal(
650
+ children: ReactNodeList,
651
+ container: DOMContainer,
652
+ key: ?string = null,
653
+) {
654
+ invariant(
655
+ isValidContainer(container),
656
+ 'Target container is not a DOM element.',
657
+ );
658
+ // TODO: pass ReactDOM portal implementation as third argument
659
+ return ReactPortal.createPortal(children, container, null, key);
660
+}
661
+
662
var ReactDOMFiber = {
663
+ createPortal,
664
+
665
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
666
// TODO: throw or warn if we couldn't hydrate?
667
return renderSubtreeIntoContainer(null, element, container, true, callback);
@@ -742,14 +757,9 @@ var ReactDOMFiber = {
757
758
findDOMNode: findDOMNode,
759
745
- unstable_createPortal(
746
- children: ReactNodeList,
747
- container: DOMContainer,
748
- key: ?string = null,
749
- ) {
750
- // TODO: pass ReactDOM portal implementation as third argument
751
- return ReactPortal.createPortal(children, container, null, key);
752
- },
760
+ // Temporary alias since we already shipped React 16 RC with it.
761
+ // TODO: remove in React 17.
762
+ unstable_createPortal: createPortal,
763
764
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
765
src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
+44
-20
@@ -182,10 +182,7 @@ describe('ReactDOMFiber', () => {
182
var expectMath = {ref: el => mathEls.push(el)};
183
184
var usePortal = function(tree) {
185
- return ReactDOM.unstable_createPortal(
186
- tree,
187
- document.createElement('div'),
188
- );
185
+ return ReactDOM.createPortal(tree, document.createElement('div'));
186
};
187
188
var assertNamespacesMatch = function(tree) {
@@ -212,6 +209,24 @@ describe('ReactDOMFiber', () => {
209
it('should render one portal', () => {
210
var portalContainer = document.createElement('div');
211
212
+ ReactDOM.render(
213
+ <div>
214
+ {ReactDOM.createPortal(<div>portal</div>, portalContainer)}
215
+ </div>,
216
+ container,
217
+ );
218
+ expect(portalContainer.innerHTML).toBe('<div>portal</div>');
219
+ expect(container.innerHTML).toBe('<div></div>');
220
+
221
+ ReactDOM.unmountComponentAtNode(container);
222
+ expect(portalContainer.innerHTML).toBe('');
223
+ expect(container.innerHTML).toBe('');
224
+ });
225
+
226
+ // TODO: remove in React 17
227
+ it('should support unstable_createPortal alias', () => {
228
+ var portalContainer = document.createElement('div');
229
+
230
ReactDOM.render(
231
<div>
232
{ReactDOM.unstable_createPortal(<div>portal</div>, portalContainer)}
@@ -260,12 +275,12 @@ describe('ReactDOMFiber', () => {
275
const {step} = this.props;
276
return [
277
<Child key="a" name={`normal[0]:${step}`} />,
263
- ReactDOM.unstable_createPortal(
278
+ ReactDOM.createPortal(
279
<Child key="b" name={`portal1[0]:${step}`} />,
280
portalContainer1,
281
),
282
<Child key="c" name={`normal[1]:${step}`} />,
268
- ReactDOM.unstable_createPortal(
283
+ ReactDOM.createPortal(
284
[
285
<Child key="d" name={`portal2[0]:${step}`} />,
286
<Child key="e" name={`portal2[1]:${step}`} />,
@@ -334,14 +349,14 @@ describe('ReactDOMFiber', () => {
349
ReactDOM.render(
350
[
351
<div key="a">normal[0]</div>,
337
- ReactDOM.unstable_createPortal(
352
+ ReactDOM.createPortal(
353
[
354
<div key="b">portal1[0]</div>,
340
- ReactDOM.unstable_createPortal(
355
+ ReactDOM.createPortal(
356
<div key="c">portal2[0]</div>,
357
portalContainer2,
358
),
344
- ReactDOM.unstable_createPortal(
359
+ ReactDOM.createPortal(
360
<div key="d">portal3[0]</div>,
361
portalContainer3,
362
),
@@ -374,7 +389,7 @@ describe('ReactDOMFiber', () => {
389
390
ReactDOM.render(
391
<div>
377
- {ReactDOM.unstable_createPortal(<div>portal:1</div>, portalContainer)}
392
+ {ReactDOM.createPortal(<div>portal:1</div>, portalContainer)}
393
</div>,
394
container,
395
);
@@ -383,7 +398,7 @@ describe('ReactDOMFiber', () => {
398
399
ReactDOM.render(
400
<div>
386
- {ReactDOM.unstable_createPortal(<div>portal:2</div>, portalContainer)}
401
+ {ReactDOM.createPortal(<div>portal:2</div>, portalContainer)}
402
</div>,
403
container,
404
);
@@ -392,7 +407,7 @@ describe('ReactDOMFiber', () => {
407
408
ReactDOM.render(
409
<div>
395
- {ReactDOM.unstable_createPortal(<p>portal:3</p>, portalContainer)}
410
+ {ReactDOM.createPortal(<p>portal:3</p>, portalContainer)}
411
</div>,
412
container,
413
);
@@ -401,7 +416,7 @@ describe('ReactDOMFiber', () => {
416
417
ReactDOM.render(
418
<div>
404
- {ReactDOM.unstable_createPortal(['Hi', 'Bye'], portalContainer)}
419
+ {ReactDOM.createPortal(['Hi', 'Bye'], portalContainer)}
420
</div>,
421
container,
422
);
@@ -410,7 +425,7 @@ describe('ReactDOMFiber', () => {
425
426
ReactDOM.render(
427
<div>
413
- {ReactDOM.unstable_createPortal(['Bye', 'Hi'], portalContainer)}
428
+ {ReactDOM.createPortal(['Bye', 'Hi'], portalContainer)}
429
</div>,
430
container,
431
);
@@ -419,7 +434,7 @@ describe('ReactDOMFiber', () => {
434
435
ReactDOM.render(
436
<div>
422
- {ReactDOM.unstable_createPortal(null, portalContainer)}
437
+ {ReactDOM.createPortal(null, portalContainer)}
438
</div>,
439
container,
440
);
@@ -700,7 +715,7 @@ describe('ReactDOMFiber', () => {
715
}
716
717
render() {
703
- return ReactDOM.unstable_createPortal(<Component />, portalContainer);
718
+ return ReactDOM.createPortal(<Component />, portalContainer);
719
}
720
}
721
@@ -741,7 +756,7 @@ describe('ReactDOMFiber', () => {
756
}
757
758
render() {
744
- return ReactDOM.unstable_createPortal(<Component />, portalContainer);
759
+ return ReactDOM.createPortal(<Component />, portalContainer);
760
}
761
}
762
@@ -781,7 +796,7 @@ describe('ReactDOMFiber', () => {
796
}
797
798
render() {
784
- return ReactDOM.unstable_createPortal(<Component />, portalContainer);
799
+ return ReactDOM.createPortal(<Component />, portalContainer);
800
}
801
}
802
@@ -821,7 +836,7 @@ describe('ReactDOMFiber', () => {
836
837
ReactDOM.render(
838
<div onClick={() => ops.push('parent clicked')}>
824
- {ReactDOM.unstable_createPortal(
839
+ {ReactDOM.createPortal(
840
<div
841
onClick={() => ops.push('portal clicked')}
842
ref={n => (portal = n)}>
@@ -874,7 +889,7 @@ describe('ReactDOMFiber', () => {
889
onMouseEnter={() => ops.push('enter parent')}
890
onMouseLeave={() => ops.push('leave parent')}>
891
<div ref={n => (firstTarget = n)} />
877
- {ReactDOM.unstable_createPortal(
892
+ {ReactDOM.createPortal(
893
<div
894
onMouseEnter={() => ops.push('enter portal')}
895
onMouseLeave={() => ops.push('leave portal')}
@@ -909,6 +924,15 @@ describe('ReactDOMFiber', () => {
924
]);
925
});
926
927
+ it('should throw on bad createPortal argument', () => {
928
+ expect(() => {
929
+ ReactDOM.createPortal(<div>portal</div>, null);
930
+ }).toThrow('Target container is not a DOM element.');
931
+ expect(() => {
932
+ ReactDOM.createPortal(<div>portal</div>, document.createTextNode('hi'));
933
+ }).toThrow('Target container is not a DOM element.');
934
+ });
935
+
936
it('should warn for non-functional event listeners', () => {
937
spyOn(console, 'error');
938
class Example extends React.Component {
src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js
+1
-1
@@ -319,7 +319,7 @@ describe('renderSubtreeIntoContainer', () => {
319
'a React 15 tree inside a React 16 tree using ' +
320
"unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
321
'make sure you have only one copy of React (and ideally, switch to ' +
322
- 'ReactDOM.unstable_createPortal).',
322
+ 'ReactDOM.createPortal).',
323
);
324
});
325
}
src/renderers/native/ReactNativeFiberEntry.js
+1
-1
@@ -79,7 +79,7 @@ const ReactNativeFiber: ReactNativeType = {
79
UIManager.removeRootView(containerTag);
80
},
81
82
- unstable_createPortal(
82
+ createPortal(
83
children: ReactNodeList,
84
containerTag: number,
85
key: ?string = null,
src/renderers/shared/fiber/ReactFiberClassComponent.js
+1
-1
@@ -71,7 +71,7 @@ if (__DEV__) {
71
'a React 15 tree inside a React 16 tree using ' +
72
"unstable_renderSubtreeIntoContainer, which isn't supported. Try " +
73
'to make sure you have only one copy of React (and ideally, switch ' +
74
- 'to ReactDOM.unstable_createPortal).',
74
+ 'to ReactDOM.createPortal).',
75
);
76
},
77
});