Remove unstable_asyncRender() API in favor of always using a wrapper
Andrew Clark committed
Apr 18, 2017 at 14:46 UTC
f69748601d5c1fd1fc3585d6d1d1970c2c13a272
6 files changed
+142
-214
src/renderers/dom/fiber/ReactDOMFiber.js
+63
-106
@@ -387,7 +387,6 @@ function renderSubtreeIntoContainer(
387
parentComponent: ?ReactComponent<any, any, any>,
388
children: ReactNodeList,
389
containerNode: DOMContainerElement | Document,
390
- async: boolean,
390
callback: ?Function,
391
) {
392
validateContainer(containerNode);
@@ -401,104 +400,84 @@ function renderSubtreeIntoContainer(
400
while (container.lastChild) {
401
container.removeChild(container.lastChild);
402
}
404
- const newRoot = async
405
- ? DOMRenderer.createAsyncContainer(container)
406
- : DOMRenderer.createContainer(container);
403
+ const newRoot = DOMRenderer.createContainer(container);
404
root = (container._reactRootContainer = newRoot);
405
// Initial mount should not be batched.
406
DOMRenderer.unbatchedUpdates(() => {
407
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
408
});
409
} else {
413
- if (async) {
414
- DOMRenderer.updateAsyncContainer(
415
- children,
416
- root,
417
- parentComponent,
418
- callback,
419
- );
420
- } else {
421
- DOMRenderer.updateContainer(children, root, parentComponent, callback);
422
- }
410
+ DOMRenderer.updateContainer(children, root, parentComponent, callback);
411
}
412
return DOMRenderer.getPublicRootInstance(root);
413
}
414
427
-function render(
428
- element: ReactElement<any>,
429
- container: DOMContainerElement,
430
- async: boolean,
431
- callback: ?Function,
432
-) {
433
- validateContainer(container);
434
-
435
- if (ReactFeatureFlags.disableNewFiberFeatures) {
436
- // Top-level check occurs here instead of inside child reconciler because
437
- // because requirements vary between renderers. E.g. React Art
438
- // allows arrays.
439
- if (!isValidElement(element)) {
440
- if (typeof element === 'string') {
441
- invariant(
442
- false,
443
- 'ReactDOM.render(): Invalid component element. Instead of ' +
444
- "passing a string like 'div', pass " +
445
- "React.createElement('div') or <div />.",
446
- );
447
- } else if (typeof element === 'function') {
448
- invariant(
449
- false,
450
- 'ReactDOM.render(): Invalid component element. Instead of ' +
451
- 'passing a class like Foo, pass React.createElement(Foo) ' +
452
- 'or <Foo />.',
453
- );
454
- } else if (element != null && typeof element.props !== 'undefined') {
455
- // Check if it quacks like an element
456
- invariant(
457
- false,
458
- 'ReactDOM.render(): Invalid component element. This may be ' +
459
- 'caused by unintentionally loading two independent copies ' +
460
- 'of React.',
461
- );
462
- } else {
463
- invariant(false, 'ReactDOM.render(): Invalid component element.');
464
- }
465
- }
466
- }
467
-
468
- if (__DEV__) {
469
- const isRootRenderedBySomeReact = !!container._reactRootContainer;
470
- const rootEl = getReactRootElementInContainer(container);
471
- const hasNonRootReactChild = !!(rootEl &&
472
- ReactDOMComponentTree.getInstanceFromNode(rootEl));
473
-
474
- warning(
475
- !hasNonRootReactChild || isRootRenderedBySomeReact,
476
- 'render(...): Replacing React-rendered children with a new root ' +
477
- 'component. If you intended to update the children of this node, ' +
478
- 'you should instead have the existing children update their state ' +
479
- 'and render the new components instead of calling ReactDOM.render.',
480
- );
481
-
482
- warning(
483
- !container.tagName || container.tagName.toUpperCase() !== 'BODY',
484
- 'render(): Rendering components directly into document.body is ' +
485
- 'discouraged, since its children are often manipulated by third-party ' +
486
- 'scripts and browser extensions. This may lead to subtle ' +
487
- 'reconciliation issues. Try rendering into a container element created ' +
488
- 'for your app.',
489
- );
490
- }
491
-
492
- return renderSubtreeIntoContainer(null, element, container, async, callback);
493
-}
494
-
415
var ReactDOM = {
416
render(
417
element: ReactElement<any>,
418
container: DOMContainerElement,
419
callback: ?Function,
420
) {
501
- return render(element, container, false, callback);
421
+ validateContainer(container);
422
+
423
+ if (ReactFeatureFlags.disableNewFiberFeatures) {
424
+ // Top-level check occurs here instead of inside child reconciler because
425
+ // because requirements vary between renderers. E.g. React Art
426
+ // allows arrays.
427
+ if (!isValidElement(element)) {
428
+ if (typeof element === 'string') {
429
+ invariant(
430
+ false,
431
+ 'ReactDOM.render(): Invalid component element. Instead of ' +
432
+ "passing a string like 'div', pass " +
433
+ "React.createElement('div') or <div />.",
434
+ );
435
+ } else if (typeof element === 'function') {
436
+ invariant(
437
+ false,
438
+ 'ReactDOM.render(): Invalid component element. Instead of ' +
439
+ 'passing a class like Foo, pass React.createElement(Foo) ' +
440
+ 'or <Foo />.',
441
+ );
442
+ } else if (element != null && typeof element.props !== 'undefined') {
443
+ // Check if it quacks like an element
444
+ invariant(
445
+ false,
446
+ 'ReactDOM.render(): Invalid component element. This may be ' +
447
+ 'caused by unintentionally loading two independent copies ' +
448
+ 'of React.',
449
+ );
450
+ } else {
451
+ invariant(false, 'ReactDOM.render(): Invalid component element.');
452
+ }
453
+ }
454
+ }
455
+
456
+ if (__DEV__) {
457
+ const isRootRenderedBySomeReact = !!container._reactRootContainer;
458
+ const rootEl = getReactRootElementInContainer(container);
459
+ const hasNonRootReactChild = !!(rootEl &&
460
+ ReactDOMComponentTree.getInstanceFromNode(rootEl));
461
+
462
+ warning(
463
+ !hasNonRootReactChild || isRootRenderedBySomeReact,
464
+ 'render(...): Replacing React-rendered children with a new root ' +
465
+ 'component. If you intended to update the children of this node, ' +
466
+ 'you should instead have the existing children update their state ' +
467
+ 'and render the new components instead of calling ReactDOM.render.',
468
+ );
469
+
470
+ warning(
471
+ !container.tagName || container.tagName.toUpperCase() !== 'BODY',
472
+ 'render(): Rendering components directly into document.body is ' +
473
+ 'discouraged, since its children are often manipulated by third-party ' +
474
+ 'scripts and browser extensions. This may lead to subtle ' +
475
+ 'reconciliation issues. Try rendering into a container element created ' +
476
+ 'for your app.',
477
+ );
478
+ }
479
+
480
+ return renderSubtreeIntoContainer(null, element, container, callback);
481
},
482
483
unstable_renderSubtreeIntoContainer(
@@ -515,17 +494,10 @@ var ReactDOM = {
494
parentComponent,
495
element,
496
containerNode,
518
- false,
497
callback,
498
);
499
},
500
523
- unstable_asyncRender: (null: ?(
524
- element: ReactElement<any>,
525
- container: DOMContainerElement,
526
- callback: ?Function,
527
- ) => *),
528
-
501
unmountComponentAtNode(container: DOMContainerElement) {
502
invariant(
503
isValidContainer(container),
@@ -547,7 +519,7 @@ var ReactDOM = {
519
520
// Unmount should not be batched.
521
return DOMRenderer.unbatchedUpdates(() => {
550
- return renderSubtreeIntoContainer(null, null, container, false, () => {
522
+ return renderSubtreeIntoContainer(null, null, container, () => {
523
container._reactRootContainer = null;
524
});
525
});
@@ -581,21 +553,6 @@ var ReactDOM = {
553
},
554
};
555
584
-if (ReactDOMFeatureFlags.enableAsyncSubtreeAPI) {
585
- ReactDOM.unstable_asyncRender = function(
586
- element: ReactElement<any>,
587
- container: DOMContainerElement,
588
- callback: ?Function,
589
- ) {
590
- return render(element, container, true, callback);
591
- };
592
-} else {
593
- // We set this to null on the ReactDOM export, then delete if the feature
594
- // flag is not enabled so that it's undiscoverable.
595
- // TODO: Is there a better way to satisfy Flow?
596
- delete ReactDOM.unstable_asyncRender;
597
-}
598
-
556
if (typeof injectInternals === 'function') {
557
injectInternals({
558
findFiberByHostInstance: ReactDOMComponentTree.getClosestInstanceFromNode,
src/renderers/dom/fiber/__tests__/ReactDOMFiberAsync-test.js
+37
-25
@@ -38,32 +38,19 @@ describe('ReactDOMFiberAsync', () => {
38
ReactDOM = require('react-dom');
39
});
40
41
- it('unstable_asyncRender creates an async tree', () => {
42
- ReactDOM.unstable_asyncRender(<div>Hi</div>, container);
43
- expect(container.textContent).toEqual('');
44
- jest.runAllTimers();
45
- expect(container.textContent).toEqual('Hi');
46
-
47
- ReactDOM.unstable_asyncRender(<div>Bye</div>, container);
48
- expect(container.textContent).toEqual('Hi');
49
- jest.runAllTimers();
50
- expect(container.textContent).toEqual('Bye');
51
- });
52
-
53
- it('updates inside an async tree are async by default', () => {
41
+ it('unstable_asyncUpdates creates an async subtree', () => {
42
let instance;
43
class Component extends React.Component {
44
state = {step: 0};
45
+ static unstable_asyncUpdates = true;
46
render() {
47
instance = this;
48
return <div>{this.state.step}</div>;
49
}
50
}
51
63
- ReactDOM.unstable_asyncRender(<Component />, container);
64
- expect(container.textContent).toEqual('');
52
+ ReactDOM.render(<Component />, container);
53
jest.runAllTimers();
66
- expect(container.textContent).toEqual('0');
54
55
instance.setState({step: 1});
56
expect(container.textContent).toEqual('0');
@@ -71,11 +58,17 @@ describe('ReactDOMFiberAsync', () => {
58
expect(container.textContent).toEqual('1');
59
});
60
74
- it('unstable_asyncUpdates creates an async subtree', () => {
75
- let instance;
61
+ it('updates inside an async subtree are async by default', () => {
62
class Component extends React.Component {
63
+ static unstable_asyncUpdates = true;
64
+ render() {
65
+ return <Child />;
66
+ }
67
+ }
68
+
69
+ let instance;
70
+ class Child extends React.Component {
71
state = {step: 0};
78
- unstable_asyncUpdates = true;
72
render() {
73
instance = this;
74
return <div>{this.state.step}</div>;
@@ -91,16 +84,33 @@ describe('ReactDOMFiberAsync', () => {
84
expect(container.textContent).toEqual('1');
85
});
86
94
- it('updates inside an async subtree are async by default', () => {
95
- class Component extends React.Component {
96
- unstable_asyncUpdates = true;
87
+ it('unstable_asyncUpdates at the root makes the entire tree async', () => {
88
+ class Async extends React.Component {
89
+ static unstable_asyncUpdates = true;
90
render() {
98
- return <Child />;
91
+ return this.props.children;
92
}
93
}
94
+ ReactDOM.render(<Async><div>Hi</div></Async>, container);
95
+ expect(container.textContent).toEqual('');
96
+ jest.runAllTimers();
97
+ expect(container.textContent).toEqual('Hi');
98
+
99
+ ReactDOM.render(<Async><div>Bye</div></Async>, container);
100
+ expect(container.textContent).toEqual('Hi');
101
+ jest.runAllTimers();
102
+ expect(container.textContent).toEqual('Bye');
103
+ });
104
105
+ it('updates inside an async tree are async by default', () => {
106
+ class Async extends React.Component {
107
+ static unstable_asyncUpdates = true;
108
+ render() {
109
+ return this.props.children;
110
+ }
111
+ }
112
let instance;
103
- class Child extends React.Component {
113
+ class Component extends React.Component {
114
state = {step: 0};
115
render() {
116
instance = this;
@@ -108,8 +118,10 @@ describe('ReactDOMFiberAsync', () => {
118
}
119
}
120
111
- ReactDOM.render(<Component />, container);
121
+ ReactDOM.render(<Async><Component /></Async>, container);
122
+ expect(container.textContent).toEqual('');
123
jest.runAllTimers();
124
+ expect(container.textContent).toEqual('0');
125
126
instance.setState({step: 1});
127
expect(container.textContent).toEqual('0');
src/renderers/shared/fiber/ReactFiberBeginWork.js
+1
-1
@@ -66,7 +66,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
66
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
67
hostContext: HostContext<C, CX>,
68
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
69
- getPriorityContext: (fiber: Fiber) => PriorityLevel,
69
+ getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
70
) {
71
const {
72
shouldSetTextContent,
src/renderers/shared/fiber/ReactFiberClassComponent.js
+5
-5
@@ -60,7 +60,7 @@ if (__DEV__) {
60
61
module.exports = function(
62
scheduleUpdate: (fiber: Fiber, priorityLevel: PriorityLevel) => void,
63
- getPriorityContext: (fiber: Fiber) => PriorityLevel,
63
+ getPriorityContext: (fiber: Fiber, forceAsync: boolean) => PriorityLevel,
64
memoizeProps: (workInProgress: Fiber, props: any) => void,
65
memoizeState: (workInProgress: Fiber, state: any) => void,
66
) {
@@ -69,7 +69,7 @@ module.exports = function(
69
isMounted,
70
enqueueSetState(instance, partialState, callback) {
71
const fiber = ReactInstanceMap.get(instance);
72
- const priorityLevel = getPriorityContext(fiber);
72
+ const priorityLevel = getPriorityContext(fiber, false);
73
callback = callback === undefined ? null : callback;
74
if (__DEV__) {
75
warnOnInvalidCallback(callback, 'setState');
@@ -79,7 +79,7 @@ module.exports = function(
79
},
80
enqueueReplaceState(instance, state, callback) {
81
const fiber = ReactInstanceMap.get(instance);
82
- const priorityLevel = getPriorityContext(fiber);
82
+ const priorityLevel = getPriorityContext(fiber, false);
83
callback = callback === undefined ? null : callback;
84
if (__DEV__) {
85
warnOnInvalidCallback(callback, 'replaceState');
@@ -89,7 +89,7 @@ module.exports = function(
89
},
90
enqueueForceUpdate(instance, callback) {
91
const fiber = ReactInstanceMap.get(instance);
92
- const priorityLevel = getPriorityContext(fiber);
92
+ const priorityLevel = getPriorityContext(fiber, false);
93
callback = callback === undefined ? null : callback;
94
if (__DEV__) {
95
warnOnInvalidCallback(callback, 'forceUpdate');
@@ -308,7 +308,7 @@ module.exports = function(
308
instance.refs = emptyObject;
309
instance.context = getMaskedContext(workInProgress, unmaskedContext);
310
311
- if (instance.unstable_asyncUpdates === true) {
311
+ if (workInProgress.type.unstable_asyncUpdates === true) {
312
workInProgress.internalContextTag |= AsyncUpdates;
313
}
314
src/renderers/shared/fiber/ReactFiberReconciler.js
+29
-75
@@ -124,19 +124,12 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
124
125
export type Reconciler<C, I, TI> = {
126
createContainer(containerInfo: C): OpaqueRoot,
127
- createAsyncContainer(containerInfo: C): OpaqueRoot,
127
updateContainer(
128
element: ReactNodeList,
129
container: OpaqueRoot,
130
parentComponent: ?ReactComponent<any, any, any>,
131
callback: ?Function,
132
): void,
134
- updateAsyncContainer(
135
- element: ReactNodeList,
136
- container: OpaqueRoot,
137
- parentComponent: ?ReactComponent<any, any, any>,
138
- callback: ?Function,
139
- ): void,
133
performWithPriority(priorityLevel: PriorityLevel, fn: Function): void,
134
batchedUpdates<A>(fn: () => A): A,
135
unbatchedUpdates<A>(fn: () => A): A,
@@ -193,7 +186,13 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
186
}
187
}
188
196
- const priorityLevel = getPriorityContext(current);
189
+ // Check if the top-level element is an async wrapper component. If so, treat
190
+ // updates to the root as async. This is a bit weird but lets us avoid a separate
191
+ // `renderAsync` API.
192
+ const forceAsync = element != null &&
193
+ element.type != null &&
194
+ (element.type: any).unstable_asyncUpdates === true;
195
+ const priorityLevel = getPriorityContext(current, forceAsync);
196
const nextState = {element};
197
callback = callback === undefined ? null : callback;
198
if (__DEV__) {
@@ -208,85 +207,40 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
207
scheduleUpdate(current, priorityLevel);
208
}
209
211
- function updateContainer(
212
- element: ReactNodeList,
213
- container: OpaqueRoot,
214
- parentComponent: ?ReactComponent<any, any, any>,
215
- async: boolean,
216
- callback: ?Function,
217
- ) {
218
- // TODO: Make messages more user-friendly?
219
- if (__DEV__) {
220
- warning(
221
- !async || container.current.internalContextTag & AsyncUpdates,
222
- 'Attempted to schedule an asynchronous update on a sync container.',
223
- );
224
- }
225
-
226
- // TODO: If this is a nested container, this won't be the root.
227
- const current = container.current;
228
-
229
- if (__DEV__) {
230
- if (ReactFiberInstrumentation.debugTool) {
231
- if (current.alternate === null) {
232
- ReactFiberInstrumentation.debugTool.onMountContainer(container);
233
- } else if (element === null) {
234
- ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
235
- } else {
236
- ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
237
- }
238
- }
239
- }
240
-
241
- const context = getContextForSubtree(parentComponent);
242
- if (container.context === null) {
243
- container.context = context;
244
- } else {
245
- container.pendingContext = context;
246
- }
247
-
248
- scheduleTopLevelUpdate(current, element, callback);
249
- }
250
-
210
return {
211
createContainer(containerInfo: C): OpaqueRoot {
212
return createFiberRoot(containerInfo);
213
},
214
256
- createAsyncContainer(containerInfo: C): OpaqueRoot {
257
- const fiberRoot = createFiberRoot(containerInfo);
258
- fiberRoot.current.internalContextTag |= AsyncUpdates;
259
- return fiberRoot;
260
- },
261
-
215
updateContainer(
216
element: ReactNodeList,
217
container: OpaqueRoot,
218
parentComponent: ?ReactComponent<any, any, any>,
219
callback: ?Function,
220
): void {
268
- updateContainer(
269
- element,
270
- container,
271
- parentComponent,
272
- false, // async = false
273
- callback,
274
- );
275
- },
221
+ // TODO: If this is a nested container, this won't be the root.
222
+ const current = container.current;
223
+
224
+ if (__DEV__) {
225
+ if (ReactFiberInstrumentation.debugTool) {
226
+ if (current.alternate === null) {
227
+ ReactFiberInstrumentation.debugTool.onMountContainer(container);
228
+ } else if (element === null) {
229
+ ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
230
+ } else {
231
+ ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
232
+ }
233
+ }
234
+ }
235
277
- updateAsyncContainer(
278
- element: ReactNodeList,
279
- container: OpaqueRoot,
280
- parentComponent: ?ReactComponent<any, any, any>,
281
- callback: ?Function,
282
- ) {
283
- updateContainer(
284
- element,
285
- container,
286
- parentComponent,
287
- true, // async = true
288
- callback,
289
- );
236
+ const context = getContextForSubtree(parentComponent);
237
+ if (container.context === null) {
238
+ container.context = context;
239
+ } else {
240
+ container.pendingContext = context;
241
+ }
242
+
243
+ scheduleTopLevelUpdate(current, element, callback);
244
},
245
246
performWithPriority,
src/renderers/shared/fiber/ReactFiberScheduler.js
+7
-2
@@ -1345,12 +1345,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
1345
}
1346
}
1347
1348
- function getPriorityContext(fiber: Fiber): PriorityLevel {
1348
+ function getPriorityContext(
1349
+ fiber: Fiber,
1350
+ forceAsync: boolean,
1351
+ ): PriorityLevel {
1352
let priorityLevel = priorityContext;
1353
if (priorityLevel === NoWork) {
1354
if (
1355
!useSyncScheduling ||
1353
- (enableAsyncSubtreeAPI === true && fiber.internalContextTag & AsyncUpdates)
1356
+ (enableAsyncSubtreeAPI === true &&
1357
+ fiber.internalContextTag & AsyncUpdates) ||
1358
+ forceAsync
1359
) {
1360
priorityLevel = LowPriority;
1361
} else {