Remove <ConcurrentMode /> (#15532)
Use createSyncRoot instead.
Andrew Clark committed
May 13, 2019 at 16:10 UTC
83fc258f2914c1a7c14c7b98ee362934576f266b
15 files changed
+118
-686
fixtures/unstable-async/time-slicing/src/index.js
+5
-9
@@ -1,6 +1,6 @@
1
import React, {PureComponent} from 'react';
2
-import {flushSync, render} from 'react-dom';
3
-import {unstable_scheduleCallback} from 'scheduler';
2
+import {flushSync, unstable_createRoot} from 'react-dom';
3
+import Scheduler from 'scheduler';
4
import _ from 'lodash';
5
import Charts from './Charts';
6
import Clock from './Clock';
@@ -67,7 +67,7 @@ class App extends PureComponent {
67
}
68
this._ignoreClick = true;
69
70
- unstable_scheduleCallback(() => {
70
+ Scheduler.unstable_next(() => {
71
this.setState({showDemo: true}, () => {
72
this._ignoreClick = false;
73
});
@@ -146,9 +146,5 @@ class App extends PureComponent {
146
}
147
148
const container = document.getElementById('root');
149
-render(
150
- <React.unstable_ConcurrentMode>
151
- <App />
152
- </React.unstable_ConcurrentMode>,
153
- container
154
-);
149
+const root = ReactDOM.unstable_createRoot(container);
150
+root.render(<App />, container);
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+11
-100
@@ -16,8 +16,6 @@ let ReactFeatureFlags = require('shared/ReactFeatureFlags');
16
let ReactDOM;
17
let Scheduler;
18
19
-const ConcurrentMode = React.unstable_ConcurrentMode;
20
-
19
const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
20
HTMLInputElement.prototype,
21
'value',
@@ -86,12 +84,9 @@ describe('ReactDOMFiberAsync', () => {
84
);
85
}
86
}
89
- ReactDOM.render(
90
- <ConcurrentMode>
91
- <Counter />
92
- </ConcurrentMode>,
93
- container,
94
- );
87
+ const root = ReactDOM.unstable_createRoot(container);
88
+ root.render(<Counter />);
89
+ Scheduler.flushAll();
90
expect(asyncValueRef.current.textContent).toBe('');
91
expect(syncValueRef.current.textContent).toBe('');
92
@@ -108,34 +103,7 @@ describe('ReactDOMFiberAsync', () => {
103
expect(syncValueRef.current.textContent).toBe('hello');
104
});
105
111
- describe('with feature flag disabled', () => {
112
- beforeEach(() => {
113
- jest.resetModules();
114
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
115
- ReactDOM = require('react-dom');
116
- Scheduler = require('scheduler');
117
- });
118
-
119
- it('renders synchronously', () => {
120
- ReactDOM.render(
121
- <ConcurrentMode>
122
- <div>Hi</div>
123
- </ConcurrentMode>,
124
- container,
125
- );
126
- expect(container.textContent).toEqual('Hi');
127
-
128
- ReactDOM.render(
129
- <ConcurrentMode>
130
- <div>Bye</div>
131
- </ConcurrentMode>,
132
- container,
133
- );
134
- expect(container.textContent).toEqual('Bye');
135
- });
136
- });
137
-
138
- describe('with feature flag enabled', () => {
106
+ describe('concurrent mode', () => {
107
beforeEach(() => {
108
jest.resetModules();
109
ReactFeatureFlags = require('shared/ReactFeatureFlags');
@@ -144,7 +112,7 @@ describe('ReactDOMFiberAsync', () => {
112
Scheduler = require('scheduler');
113
});
114
147
- it('createRoot makes the entire tree async', () => {
115
+ it('top-level updates are concurrent', () => {
116
const root = ReactDOM.unstable_createRoot(container);
117
root.render(<div>Hi</div>);
118
expect(container.textContent).toEqual('');
@@ -157,7 +125,7 @@ describe('ReactDOMFiberAsync', () => {
125
expect(container.textContent).toEqual('Bye');
126
});
127
160
- it('updates inside an async tree are async by default', () => {
128
+ it('deep updates (setState) are oncurrent', () => {
129
let instance;
130
class Component extends React.Component {
131
state = {step: 0};
@@ -179,56 +147,6 @@ describe('ReactDOMFiberAsync', () => {
147
expect(container.textContent).toEqual('1');
148
});
149
182
- it('ConcurrentMode creates an async subtree', () => {
183
- let instance;
184
- class Component extends React.Component {
185
- state = {step: 0};
186
- render() {
187
- instance = this;
188
- return <div>{this.state.step}</div>;
189
- }
190
- }
191
-
192
- ReactDOM.render(
193
- <ConcurrentMode>
194
- <Component />
195
- </ConcurrentMode>,
196
- container,
197
- );
198
- Scheduler.flushAll();
199
-
200
- instance.setState({step: 1});
201
- expect(container.textContent).toEqual('0');
202
- Scheduler.flushAll();
203
- expect(container.textContent).toEqual('1');
204
- });
205
-
206
- it('updates inside an async subtree are async by default', () => {
207
- let instance;
208
- class Child extends React.Component {
209
- state = {step: 0};
210
- render() {
211
- instance = this;
212
- return <div>{this.state.step}</div>;
213
- }
214
- }
215
-
216
- ReactDOM.render(
217
- <div>
218
- <ConcurrentMode>
219
- <Child />
220
- </ConcurrentMode>
221
- </div>,
222
- container,
223
- );
224
- Scheduler.flushAll();
225
-
226
- instance.setState({step: 1});
227
- expect(container.textContent).toEqual('0');
228
- Scheduler.flushAll();
229
- expect(container.textContent).toEqual('1');
230
- });
231
-
150
it('flushSync batches sync updates and flushes them at the end of the batch', () => {
151
let ops = [];
152
let instance;
@@ -345,12 +263,8 @@ describe('ReactDOMFiberAsync', () => {
263
}
264
}
265
348
- ReactDOM.render(
349
- <ConcurrentMode>
350
- <Component />
351
- </ConcurrentMode>,
352
- container,
353
- );
266
+ const root = ReactDOM.unstable_createRoot(container);
267
+ root.render(<Component />);
268
Scheduler.flushAll();
269
270
// Updates are async by default
@@ -390,12 +304,9 @@ describe('ReactDOMFiberAsync', () => {
304
return this.state.counter;
305
}
306
}
393
- ReactDOM.render(
394
- <ConcurrentMode>
395
- <Counter />
396
- </ConcurrentMode>,
397
- container,
398
- );
307
+ const root = ReactDOM.unstable_createRoot(container);
308
+ root.render(<Counter />);
309
+ Scheduler.flushAll();
310
expect(container.textContent).toEqual('0');
311
312
// Test that a normal update is async
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+2
-6
@@ -141,7 +141,7 @@ describe('ReactDOMHooks', () => {
141
expect(labelRef.current.innerHTML).toBe('abc');
142
});
143
144
- it('should not bail out when an update is scheduled from within an event handler in ConcurrentMode', () => {
144
+ it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
145
const {createRef, useCallback, useState} = React;
146
147
const Example = ({inputRef, labelRef}) => {
@@ -162,11 +162,7 @@ describe('ReactDOMHooks', () => {
162
const labelRef = createRef();
163
164
const root = ReactDOM.unstable_createRoot(container);
165
- root.render(
166
- <React.unstable_ConcurrentMode>
167
- <Example inputRef={inputRef} labelRef={labelRef} />
168
- </React.unstable_ConcurrentMode>,
169
- );
165
+ root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
166
167
Scheduler.flushAll();
168
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+5
-11
@@ -13,7 +13,6 @@ let React = require('react');
13
let ReactDOM = require('react-dom');
14
let ReactDOMServer = require('react-dom/server');
15
let Scheduler = require('scheduler');
16
-let ConcurrentMode = React.unstable_ConcurrentMode;
16
17
describe('ReactDOMRoot', () => {
18
let container;
@@ -25,7 +24,6 @@ describe('ReactDOMRoot', () => {
24
ReactDOM = require('react-dom');
25
ReactDOMServer = require('react-dom/server');
26
Scheduler = require('scheduler');
28
- ConcurrentMode = React.unstable_ConcurrentMode;
27
});
28
29
it('renders children', () => {
@@ -47,7 +45,7 @@ describe('ReactDOMRoot', () => {
45
46
it('`root.render` returns a thenable work object', () => {
47
const root = ReactDOM.unstable_createRoot(container);
50
- const work = root.render(<ConcurrentMode>Hi</ConcurrentMode>);
48
+ const work = root.render('Hi');
49
let ops = [];
50
work.then(() => {
51
ops.push('inside callback: ' + container.textContent);
@@ -65,7 +63,7 @@ describe('ReactDOMRoot', () => {
63
64
it('resolves `work.then` callback synchronously if the work already committed', () => {
65
const root = ReactDOM.unstable_createRoot(container);
68
- const work = root.render(<ConcurrentMode>Hi</ConcurrentMode>);
66
+ const work = root.render('Hi');
67
Scheduler.flushAll();
68
let ops = [];
69
work.then(() => {
@@ -157,11 +155,7 @@ describe('ReactDOMRoot', () => {
155
156
const root = ReactDOM.unstable_createRoot(container);
157
const batch = root.createBatch();
160
- batch.render(
161
- <ConcurrentMode>
162
- <App />
163
- </ConcurrentMode>,
164
- );
158
+ batch.render(<App />);
159
160
Scheduler.flushAll();
161
@@ -208,7 +202,7 @@ describe('ReactDOMRoot', () => {
202
it('can wait for a batch to finish', () => {
203
const root = ReactDOM.unstable_createRoot(container);
204
const batch = root.createBatch();
211
- batch.render(<ConcurrentMode>Foo</ConcurrentMode>);
205
+ batch.render('Foo');
206
207
Scheduler.flushAll();
208
@@ -248,7 +242,7 @@ describe('ReactDOMRoot', () => {
242
243
it('can commit an empty batch', () => {
244
const root = ReactDOM.unstable_createRoot(container);
251
- root.render(<ConcurrentMode>1</ConcurrentMode>);
245
+ root.render(1);
246
247
Scheduler.advanceTime(2000);
248
// This batch has a later expiration time than the earlier update.
packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js
-68
@@ -103,72 +103,4 @@ describe('ReactDOMServerIntegration', () => {
103
expect(await render(<React.StrictMode />)).toBe(null);
104
});
105
});
106
-
107
- describe('React.unstable_ConcurrentMode', () => {
108
- itRenders('an concurrent mode with one child', async render => {
109
- let e = await render(
110
- <React.unstable_ConcurrentMode>
111
- <div>text1</div>
112
- </React.unstable_ConcurrentMode>,
113
- );
114
- let parent = e.parentNode;
115
- expect(parent.childNodes[0].tagName).toBe('DIV');
116
- });
117
-
118
- itRenders('an concurrent mode with several children', async render => {
119
- let Header = props => {
120
- return <p>header</p>;
121
- };
122
- let Footer = props => {
123
- return (
124
- <React.unstable_ConcurrentMode>
125
- <h2>footer</h2>
126
- <h3>about</h3>
127
- </React.unstable_ConcurrentMode>
128
- );
129
- };
130
- let e = await render(
131
- <React.unstable_ConcurrentMode>
132
- <div>text1</div>
133
- <span>text2</span>
134
- <Header />
135
- <Footer />
136
- </React.unstable_ConcurrentMode>,
137
- );
138
- let parent = e.parentNode;
139
- expect(parent.childNodes[0].tagName).toBe('DIV');
140
- expect(parent.childNodes[1].tagName).toBe('SPAN');
141
- expect(parent.childNodes[2].tagName).toBe('P');
142
- expect(parent.childNodes[3].tagName).toBe('H2');
143
- expect(parent.childNodes[4].tagName).toBe('H3');
144
- });
145
-
146
- itRenders('a nested concurrent mode', async render => {
147
- let e = await render(
148
- <React.unstable_ConcurrentMode>
149
- <React.unstable_ConcurrentMode>
150
- <div>text1</div>
151
- </React.unstable_ConcurrentMode>
152
- <span>text2</span>
153
- <React.unstable_ConcurrentMode>
154
- <React.unstable_ConcurrentMode>
155
- <React.unstable_ConcurrentMode>
156
- {null}
157
- <p />
158
- </React.unstable_ConcurrentMode>
159
- {false}
160
- </React.unstable_ConcurrentMode>
161
- </React.unstable_ConcurrentMode>
162
- </React.unstable_ConcurrentMode>,
163
- );
164
- let parent = e.parentNode;
165
- expect(parent.childNodes[0].tagName).toBe('DIV');
166
- expect(parent.childNodes[1].tagName).toBe('SPAN');
167
- expect(parent.childNodes[2].tagName).toBe('P');
168
- });
169
-
170
- itRenders('an empty concurrent mode', async render => {
171
- expect(await render(<React.unstable_ConcurrentMode />)).toBe(null);
172
- });
173
- });
106
});
packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.internal.js
+46
-19
@@ -374,7 +374,7 @@ describe('SimpleEventPlugin', function() {
374
expect(button.textContent).toEqual('Count: 7');
375
});
376
377
- it('flushes lowest pending interactive priority', () => {
377
+ it('flushes discrete updates in order', () => {
378
container = document.createElement('div');
379
document.body.appendChild(container);
380
@@ -382,15 +382,21 @@ describe('SimpleEventPlugin', function() {
382
class Button extends React.Component {
383
state = {lowPriCount: 0};
384
render() {
385
+ const text = `High-pri count: ${
386
+ this.props.highPriCount
387
+ }, Low-pri count: ${this.state.lowPriCount}`;
388
+ Scheduler.yieldValue(text);
389
return (
390
<button
391
ref={el => (button = el)}
388
- onClick={
389
- // Intentionally not using the updater form here
390
- () => this.setState({lowPriCount: this.state.lowPriCount + 1})
391
- }>
392
- High-pri count: {this.props.highPriCount}, Low-pri count:{' '}
393
- {this.state.lowPriCount}
392
+ onClick={() => {
393
+ Scheduler.unstable_next(() => {
394
+ this.setState(state => ({
395
+ lowPriCount: state.lowPriCount + 1,
396
+ }));
397
+ });
398
+ }}>
399
+ {text}
400
</button>
401
);
402
}
@@ -402,19 +408,24 @@ describe('SimpleEventPlugin', function() {
408
return (
409
<div
410
onClick={
405
- // Intentionally not using the updater form here
406
- () => this.setState({highPriCount: this.state.highPriCount + 1})
411
+ // Intentionally not using the updater form here, to test
412
+ // that updates are serially processed.
413
+ () => {
414
+ this.setState({highPriCount: this.state.highPriCount + 1});
415
+ }
416
}>
408
- <React.unstable_ConcurrentMode>
409
- <Button highPriCount={this.state.highPriCount} />
410
- </React.unstable_ConcurrentMode>
417
+ <Button highPriCount={this.state.highPriCount} />
418
</div>
419
);
420
}
421
}
422
423
// Initial mount
417
- ReactDOM.render(<Wrapper />, container);
424
+ const root = ReactDOM.unstable_createRoot(container);
425
+ root.render(<Wrapper />);
426
+ expect(Scheduler).toFlushAndYield([
427
+ 'High-pri count: 0, Low-pri count: 0',
428
+ ]);
429
expect(button.textContent).toEqual('High-pri count: 0, Low-pri count: 0');
430
431
function click() {
@@ -425,8 +436,12 @@ describe('SimpleEventPlugin', function() {
436
437
// Click the button a single time
438
click();
428
- // The high-pri counter should flush synchronously, but not the
429
- // low-pri counter
439
+ // Nothing should flush on the first click.
440
+ expect(Scheduler).toHaveYielded([]);
441
+ // Click again. This will force the previous discrete update to flush. But
442
+ // only the high-pri count will increase.
443
+ click();
444
+ expect(Scheduler).toHaveYielded(['High-pri count: 1, Low-pri count: 0']);
445
expect(button.textContent).toEqual('High-pri count: 1, Low-pri count: 0');
446
447
// Click the button many more times
@@ -437,10 +452,22 @@ describe('SimpleEventPlugin', function() {
452
click();
453
click();
454
440
- // Flush the remaining work
441
- Scheduler.flushAll();
442
- // Both counters should equal the total number of clicks
443
- expect(button.textContent).toEqual('High-pri count: 7, Low-pri count: 7');
455
+ // Flush the remaining work.
456
+ expect(Scheduler).toHaveYielded([
457
+ 'High-pri count: 2, Low-pri count: 0',
458
+ 'High-pri count: 3, Low-pri count: 0',
459
+ 'High-pri count: 4, Low-pri count: 0',
460
+ 'High-pri count: 5, Low-pri count: 0',
461
+ 'High-pri count: 6, Low-pri count: 0',
462
+ 'High-pri count: 7, Low-pri count: 0',
463
+ ]);
464
+
465
+ // At the end, both counters should equal the total number of clicks
466
+ expect(Scheduler).toFlushAndYield([
467
+ 'High-pri count: 8, Low-pri count: 0',
468
+ 'High-pri count: 8, Low-pri count: 8',
469
+ ]);
470
+ expect(button.textContent).toEqual('High-pri count: 8, Low-pri count: 8');
471
});
472
});
473
packages/react-is/README.md
-10
@@ -45,16 +45,6 @@ ReactIs.isValidElementType(React.createFactory("div")); // true
45
46
### Determining an Element's Type
47
48
-#### ConcurrentMode
49
-
50
-```js
51
-import React from "react";
52
-import * as ReactIs from 'react-is';
53
-
54
-ReactIs.isConcurrentMode(<React.unstable_ConcurrentMode />); // true
55
-ReactIs.typeOf(<React.unstable_ConcurrentMode />) === ReactIs.ConcurrentMode; // true
56
-```
57
-
48
#### Context
49
50
```js
packages/react-is/src/__tests__/ReactIs-test.js
-21
@@ -58,9 +58,6 @@ describe('ReactIs', () => {
58
true,
59
);
60
expect(ReactIs.isValidElementType(React.Fragment)).toEqual(true);
61
- expect(ReactIs.isValidElementType(React.unstable_ConcurrentMode)).toEqual(
62
- true,
63
- );
61
expect(ReactIs.isValidElementType(React.StrictMode)).toEqual(true);
62
expect(ReactIs.isValidElementType(React.Suspense)).toEqual(true);
63
@@ -72,20 +69,6 @@ describe('ReactIs', () => {
69
expect(ReactIs.isValidElementType({type: 'div', props: {}})).toEqual(false);
70
});
71
75
- it('should identify concurrent mode', () => {
76
- expect(ReactIs.typeOf(<React.unstable_ConcurrentMode />)).toBe(
77
- ReactIs.ConcurrentMode,
78
- );
79
- expect(ReactIs.isConcurrentMode(<React.unstable_ConcurrentMode />)).toBe(
80
- true,
81
- );
82
- expect(ReactIs.isConcurrentMode({type: ReactIs.ConcurrentMode})).toBe(
83
- false,
84
- );
85
- expect(ReactIs.isConcurrentMode(<React.StrictMode />)).toBe(false);
86
- expect(ReactIs.isConcurrentMode(<div />)).toBe(false);
87
- });
88
-
72
it('should identify context consumers', () => {
73
const Context = React.createContext(false);
74
expect(ReactIs.typeOf(<Context.Consumer />)).toBe(ReactIs.ContextConsumer);
@@ -117,7 +100,6 @@ describe('ReactIs', () => {
100
expect(ReactIs.isElement(<Context.Provider />)).toBe(true);
101
expect(ReactIs.isElement(<Context.Consumer />)).toBe(true);
102
expect(ReactIs.isElement(<React.Fragment />)).toBe(true);
120
- expect(ReactIs.isElement(<React.unstable_ConcurrentMode />)).toBe(true);
103
expect(ReactIs.isElement(<React.StrictMode />)).toBe(true);
104
expect(ReactIs.isElement(<React.Suspense />)).toBe(true);
105
});
@@ -127,7 +109,6 @@ describe('ReactIs', () => {
109
expect(ReactIs.typeOf(<RefForwardingComponent />)).toBe(ReactIs.ForwardRef);
110
expect(ReactIs.isForwardRef(<RefForwardingComponent />)).toBe(true);
111
expect(ReactIs.isForwardRef({type: ReactIs.StrictMode})).toBe(false);
130
- expect(ReactIs.isForwardRef(<React.unstable_ConcurrentMode />)).toBe(false);
112
expect(ReactIs.isForwardRef(<div />)).toBe(false);
113
});
114
@@ -168,7 +149,6 @@ describe('ReactIs', () => {
149
expect(ReactIs.typeOf(<React.StrictMode />)).toBe(ReactIs.StrictMode);
150
expect(ReactIs.isStrictMode(<React.StrictMode />)).toBe(true);
151
expect(ReactIs.isStrictMode({type: ReactIs.StrictMode})).toBe(false);
171
- expect(ReactIs.isStrictMode(<React.unstable_ConcurrentMode />)).toBe(false);
152
expect(ReactIs.isStrictMode(<div />)).toBe(false);
153
});
154
@@ -188,7 +168,6 @@ describe('ReactIs', () => {
168
ReactIs.isProfiler(<React.Profiler id="foo" onRender={jest.fn()} />),
169
).toBe(true);
170
expect(ReactIs.isProfiler({type: ReactIs.Profiler})).toBe(false);
191
- expect(ReactIs.isProfiler(<React.unstable_ConcurrentMode />)).toBe(false);
171
expect(ReactIs.isProfiler(<div />)).toBe(false);
172
});
173
});
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+2
-4
@@ -189,14 +189,12 @@ describe('ReactDebugFiberPerf', () => {
189
expect(getFlameChart()).toMatchSnapshot();
190
});
191
192
- it('does not include ConcurrentMode, StrictMode, or Profiler components in measurements', () => {
192
+ it('does not include StrictMode or Profiler components in measurements', () => {
193
ReactNoop.render(
194
<React.Profiler id="test" onRender={jest.fn()}>
195
<React.StrictMode>
196
<Parent>
197
- <React.unstable_ConcurrentMode>
198
- <Child />
199
- </React.unstable_ConcurrentMode>
197
+ <Child />
198
</Parent>
199
</React.StrictMode>
200
</React.Profiler>,
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+7
-277
@@ -5,8 +5,6 @@ let ReactNoop;
5
let Scheduler;
6
let ReactCache;
7
let Suspense;
8
-let StrictMode;
9
-let ConcurrentMode;
8
9
let TextResource;
10
let textResourceShouldFail;
@@ -26,8 +24,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
24
Scheduler = require('scheduler');
25
ReactCache = require('react-cache');
26
Suspense = React.Suspense;
29
- StrictMode = React.StrictMode;
30
- ConcurrentMode = React.unstable_ConcurrentMode;
27
28
TextResource = ReactCache.unstable_createResource(([text, ms = 0]) => {
29
return new Promise((resolve, reject) =>
@@ -896,7 +892,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
892
expect(ReactNoop.getChildren()).toEqual([span('Result')]);
893
});
894
899
- it('times out immediately when Suspense is in loose mode, even if the suspender is async', async () => {
895
+ it('times out immediately when Suspense is in legacy mode', async () => {
896
class UpdatingText extends React.Component {
897
state = {step: 1};
898
render() {
@@ -918,15 +914,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
914
function App() {
915
return (
916
<Suspense fallback={<Spinner />}>
921
- <ConcurrentMode>
922
- <UpdatingText ref={text} />
923
- <Text text="Sibling" />
924
- </ConcurrentMode>
917
+ <UpdatingText ref={text} />
918
+ <Text text="Sibling" />
919
</Suspense>
920
);
921
}
922
929
- // Initial mount. This is synchronous, because the root is sync.
923
+ // Initial mount.
924
ReactNoop.renderLegacySyncRoot(<App />);
925
await advanceTimers(100);
926
expect(Scheduler).toHaveYielded([
@@ -945,14 +939,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
939
</React.Fragment>,
940
);
941
948
- // Update. This starts out asynchronously.
942
+ // Update.
943
text.current.setState({step: 2}, () =>
944
Scheduler.yieldValue('Update did commit'),
945
);
946
953
- // Suspend during an async render.
954
- expect(ReactNoop.flushNextYield()).toEqual(['Suspend! [Step: 2]']);
955
- expect(Scheduler).toFlushAndYield([
947
+ expect(ReactNoop.flushNextYield()).toEqual([
948
+ 'Suspend! [Step: 2]',
949
'Loading (1)',
950
'Loading (2)',
951
'Loading (3)',
@@ -979,269 +972,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
972
);
973
});
974
982
- it(
983
- 'continues rendering asynchronously even if a promise is captured by ' +
984
- 'a sync boundary (legacy mode)',
985
- async () => {
986
- class UpdatingText extends React.Component {
987
- state = {text: this.props.initialText};
988
- render() {
989
- return this.props.children(this.state.text);
990
- }
991
- }
992
-
993
- const text1 = React.createRef(null);
994
- const text2 = React.createRef(null);
995
- function App() {
996
- return (
997
- <Fragment>
998
- <Suspense fallback={<Text text="Loading..." />}>
999
- <ConcurrentMode>
1000
- <UpdatingText ref={text1} initialText="Async: 1">
1001
- {text => (
1002
- <Fragment>
1003
- <Text text="Before" />
1004
- <AsyncText text={text} />
1005
- <Text text="After" />
1006
- </Fragment>
1007
- )}
1008
- </UpdatingText>
1009
- </ConcurrentMode>
1010
- </Suspense>
1011
- <ConcurrentMode>
1012
- <UpdatingText ref={text2} initialText="Sync: 1">
1013
- {text => (
1014
- <Fragment>
1015
- <Text text="Before" />
1016
- <Text text={text} />
1017
- <Text text="After" />
1018
- </Fragment>
1019
- )}
1020
- </UpdatingText>
1021
- </ConcurrentMode>
1022
- </Fragment>
1023
- );
1024
- }
1025
-
1026
- // Initial mount
1027
- ReactNoop.renderLegacySyncRoot(<App />, () =>
1028
- Scheduler.yieldValue('Did mount'),
1029
- );
1030
- await advanceTimers(100);
1031
-
1032
- expect(Scheduler).toHaveYielded([
1033
- 'Before',
1034
- 'Suspend! [Async: 1]',
1035
- 'After',
1036
- 'Loading...',
1037
- 'Before',
1038
- 'Sync: 1',
1039
- 'After',
1040
- 'Did mount',
1041
- 'Promise resolved [Async: 1]',
1042
- ]);
1043
- expect(Scheduler).toFlushExpired(['Async: 1']);
1044
- expect(ReactNoop).toMatchRenderedOutput(
1045
- <React.Fragment>
1046
- <span prop="Before" />
1047
- <span prop="Async: 1" />
1048
- <span prop="After" />
1049
-
1050
- <span prop="Before" />
1051
- <span prop="Sync: 1" />
1052
- <span prop="After" />
1053
- </React.Fragment>,
1054
- );
1055
-
1056
- // Update. This starts out asynchronously.
1057
- text1.current.setState({text: 'Async: 2'}, () =>
1058
- Scheduler.yieldValue('Update 1 did commit'),
1059
- );
1060
- text2.current.setState({text: 'Sync: 2'}, () =>
1061
- Scheduler.yieldValue('Update 2 did commit'),
1062
- );
1063
-
1064
- // Start rendering asynchronously
1065
- expect(Scheduler).toFlushAndYieldThrough(['Before']);
1066
-
1067
- // Now render the next child, which suspends
1068
- expect(ReactNoop.flushNextYield()).toEqual([
1069
- // This child suspends
1070
- 'Suspend! [Async: 2]',
1071
- ]);
1072
- expect(Scheduler).toFlushAndYield([
1073
- 'After',
1074
- 'Loading...',
1075
- 'Before',
1076
- 'Sync: 2',
1077
- 'After',
1078
- 'Update 1 did commit',
1079
- 'Update 2 did commit',
1080
- ]);
1081
- expect(ReactNoop).toMatchRenderedOutput(
1082
- <React.Fragment>
1083
- <span hidden={true} prop="Before" />
1084
- <span hidden={true} prop="Async: 1" />
1085
- <span hidden={true} prop="After" />
1086
- <span prop="Loading..." />
1087
-
1088
- <span prop="Before" />
1089
- <span prop="Sync: 2" />
1090
- <span prop="After" />
1091
- </React.Fragment>,
1092
- );
1093
-
1094
- // When the placeholder is pinged, the boundary must be re-rendered
1095
- // synchronously.
1096
- await advanceTimers(100);
1097
-
1098
- expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1099
- expect(Scheduler).toFlushExpired(['Async: 2']);
1100
- expect(ReactNoop).toMatchRenderedOutput(
1101
- <React.Fragment>
1102
- <span prop="Before" />
1103
- <span prop="Async: 2" />
1104
- <span prop="After" />
1105
-
1106
- <span prop="Before" />
1107
- <span prop="Sync: 2" />
1108
- <span prop="After" />
1109
- </React.Fragment>,
1110
- );
1111
- },
1112
- );
1113
-
1114
- it(
1115
- 'continues rendering asynchronously even if a promise is captured by ' +
1116
- 'a sync boundary (strict, legacy)',
1117
- async () => {
1118
- class UpdatingText extends React.Component {
1119
- state = {text: this.props.initialText};
1120
- render() {
1121
- return this.props.children(this.state.text);
1122
- }
1123
- }
1124
-
1125
- const text1 = React.createRef(null);
1126
- const text2 = React.createRef(null);
1127
- function App() {
1128
- return (
1129
- <StrictMode>
1130
- <Suspense fallback={<Text text="Loading..." />}>
1131
- <ConcurrentMode>
1132
- <UpdatingText ref={text1} initialText="Async: 1">
1133
- {text => (
1134
- <Fragment>
1135
- <Text text="Before" />
1136
- <AsyncText text={text} />
1137
- <Text text="After" />
1138
- </Fragment>
1139
- )}
1140
- </UpdatingText>
1141
- </ConcurrentMode>
1142
- </Suspense>
1143
- <ConcurrentMode>
1144
- <UpdatingText ref={text2} initialText="Sync: 1">
1145
- {text => (
1146
- <Fragment>
1147
- <Text text="Before" />
1148
- <Text text={text} />
1149
- <Text text="After" />
1150
- </Fragment>
1151
- )}
1152
- </UpdatingText>
1153
- </ConcurrentMode>
1154
- </StrictMode>
1155
- );
1156
- }
1157
-
1158
- // Initial mount
1159
- ReactNoop.renderLegacySyncRoot(<App />, () =>
1160
- Scheduler.yieldValue('Did mount'),
1161
- );
1162
- await advanceTimers(100);
1163
- expect(Scheduler).toHaveYielded([
1164
- 'Before',
1165
- 'Suspend! [Async: 1]',
1166
- 'After',
1167
- 'Loading...',
1168
- 'Before',
1169
- 'Sync: 1',
1170
- 'After',
1171
- 'Did mount',
1172
- 'Promise resolved [Async: 1]',
1173
- ]);
1174
- expect(Scheduler).toFlushExpired(['Async: 1']);
1175
- expect(ReactNoop).toMatchRenderedOutput(
1176
- <React.Fragment>
1177
- <span prop="Before" />
1178
- <span prop="Async: 1" />
1179
- <span prop="After" />
1180
-
1181
- <span prop="Before" />
1182
- <span prop="Sync: 1" />
1183
- <span prop="After" />
1184
- </React.Fragment>,
1185
- );
1186
-
1187
- // Update. This starts out asynchronously.
1188
- text1.current.setState({text: 'Async: 2'}, () =>
1189
- Scheduler.yieldValue('Update 1 did commit'),
1190
- );
1191
- text2.current.setState({text: 'Sync: 2'}, () =>
1192
- Scheduler.yieldValue('Update 2 did commit'),
1193
- );
1194
-
1195
- // Start rendering asynchronously
1196
- expect(Scheduler).toFlushAndYieldThrough(['Before']);
1197
-
1198
- // Now render the next child, which suspends
1199
- expect(ReactNoop.flushNextYield()).toEqual([
1200
- // This child suspends
1201
- 'Suspend! [Async: 2]',
1202
- ]);
1203
- expect(Scheduler).toFlushAndYield([
1204
- 'After',
1205
- 'Loading...',
1206
- 'Before',
1207
- 'Sync: 2',
1208
- 'After',
1209
- 'Update 1 did commit',
1210
- 'Update 2 did commit',
1211
- ]);
1212
- expect(ReactNoop).toMatchRenderedOutput(
1213
- <React.Fragment>
1214
- <span hidden={true} prop="Before" />
1215
- <span hidden={true} prop="Async: 1" />
1216
- <span hidden={true} prop="After" />
1217
- <span prop="Loading..." />
1218
-
1219
- <span prop="Before" />
1220
- <span prop="Sync: 2" />
1221
- <span prop="After" />
1222
- </React.Fragment>,
1223
- );
1224
-
1225
- // When the placeholder is pinged, the boundary must be re-rendered
1226
- // synchronously.
1227
- await advanceTimers(100);
1228
-
1229
- expect(Scheduler).toHaveYielded(['Promise resolved [Async: 2]']);
1230
- expect(Scheduler).toFlushExpired(['Async: 2']);
1231
- expect(ReactNoop).toMatchRenderedOutput(
1232
- <React.Fragment>
1233
- <span prop="Before" />
1234
- <span prop="Async: 2" />
1235
- <span prop="After" />
1236
-
1237
- <span prop="Before" />
1238
- <span prop="Sync: 2" />
1239
- <span prop="After" />
1240
- </React.Fragment>,
1241
- );
1242
- },
1243
- );
1244
-
975
it('does not re-render siblings in loose mode', async () => {
976
class TextWithLifecycle extends React.Component {
977
componentDidMount() {
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+1
-1
@@ -91,7 +91,7 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
91
"
92
`;
93
94
-exports[`ReactDebugFiberPerf does not include ConcurrentMode, StrictMode, or Profiler components in measurements 1`] = `
94
+exports[`ReactDebugFiberPerf does not include StrictMode or Profiler components in measurements 1`] = `
95
"⚛ (Waiting for async callback...)
96
97
// Mount
packages/react/src/React.js
+1
-14
@@ -7,7 +7,6 @@
7
8
import ReactVersion from 'shared/ReactVersion';
9
import {
10
- REACT_CONCURRENT_MODE_TYPE,
10
REACT_FRAGMENT_TYPE,
11
REACT_PROFILER_TYPE,
12
REACT_STRICT_MODE_TYPE,
@@ -51,12 +50,7 @@ import {
50
} from './ReactElementValidator';
51
import ReactSharedInternals from './ReactSharedInternals';
52
import {error, warn} from './withComponentStack';
54
-import {
55
- enableEventAPI,
56
- enableStableConcurrentModeAPIs,
57
- enableJSXTransformAPI,
58
-} from 'shared/ReactFeatureFlags';
59
-
53
+import {enableEventAPI, enableJSXTransformAPI} from 'shared/ReactFeatureFlags';
54
const React = {
55
Children: {
56
map,
@@ -101,8 +95,6 @@ const React = {
95
96
version: ReactVersion,
97
104
- unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
105
-
98
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
99
};
100
@@ -115,11 +107,6 @@ if (enableEventAPI) {
107
React.unstable_createEventComponent = createEventComponent;
108
}
109
118
-if (enableStableConcurrentModeAPIs) {
119
- React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
120
- React.unstable_ConcurrentMode = undefined;
121
-}
122
-
110
if (enableJSXTransformAPI) {
111
if (__DEV__) {
112
React.jsxDEV = jsxWithValidation;
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+36
-143
@@ -10,6 +10,7 @@
10
'use strict';
11
12
let React;
13
+let Scheduler;
14
let ReactFeatureFlags;
15
let ReactTestRenderer;
16
let PropTypes;
@@ -21,6 +22,7 @@ describe('ReactStrictMode', () => {
22
ReactFeatureFlags = require('shared/ReactFeatureFlags');
23
ReactFeatureFlags.debugRenderPhaseSideEffects = true;
24
React = require('react');
25
+ Scheduler = require('scheduler');
26
ReactTestRenderer = require('react-test-renderer');
27
});
28
@@ -153,6 +155,7 @@ describe('ReactStrictMode', () => {
155
ReactFeatureFlags = require('shared/ReactFeatureFlags');
156
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = debugRenderPhaseSideEffectsForStrictMode;
157
React = require('react');
158
+ Scheduler = require('scheduler');
159
ReactTestRenderer = require('react-test-renderer');
160
});
161
@@ -298,27 +301,16 @@ describe('ReactStrictMode', () => {
301
});
302
});
303
301
- describe('async subtree', () => {
304
+ describe('Concurrent Mode', () => {
305
beforeEach(() => {
306
jest.resetModules();
307
308
React = require('react');
309
+ Scheduler = require('scheduler');
310
ReactTestRenderer = require('react-test-renderer');
311
});
312
309
- it('should warn about unsafe legacy lifecycle methods within the tree', () => {
310
- class SyncRoot extends React.Component {
311
- UNSAFE_componentWillMount() {}
312
- UNSAFE_componentWillUpdate() {}
313
- UNSAFE_componentWillReceiveProps() {}
314
- render() {
315
- return (
316
- <React.unstable_ConcurrentMode>
317
- <AsyncRoot />
318
- </React.unstable_ConcurrentMode>
319
- );
320
- }
321
- }
313
+ it('should warn about unsafe legacy lifecycle methods anywhere in the tree', () => {
314
class AsyncRoot extends React.Component {
315
UNSAFE_componentWillMount() {}
316
UNSAFE_componentWillUpdate() {}
@@ -352,13 +344,12 @@ describe('ReactStrictMode', () => {
344
}
345
}
346
355
- let rendered;
356
- expect(() => {
357
- rendered = ReactTestRenderer.create(<SyncRoot />);
358
- }).toWarnDev(
347
+ const root = ReactTestRenderer.create(null, {
348
+ unstable_isConcurrent: true,
349
+ });
350
+ root.update(<AsyncRoot />);
351
+ expect(() => Scheduler.flushAll()).toWarnDev(
352
'Unsafe lifecycle methods were found within a strict-mode tree:' +
360
- '\n in ConcurrentMode (at **)' +
361
- '\n in SyncRoot (at **)' +
353
'\n\ncomponentWillMount: Please update the following components ' +
354
'to use componentDidMount instead: AsyncRoot' +
355
'\n\ncomponentWillReceiveProps: Please update the following components ' +
@@ -367,26 +358,15 @@ describe('ReactStrictMode', () => {
358
'to use componentDidUpdate instead: AsyncRoot' +
359
'\n\nLearn more about this warning here:' +
360
'\nhttps://fb.me/react-strict-mode-warnings',
361
+ {withoutStack: true},
362
);
363
364
// Dedupe
373
- rendered = ReactTestRenderer.create(<SyncRoot />);
374
- rendered.update(<SyncRoot />);
365
+ root.update(<AsyncRoot />);
366
+ Scheduler.flushAll();
367
});
368
369
it('should coalesce warnings by lifecycle name', () => {
378
- class SyncRoot extends React.Component {
379
- UNSAFE_componentWillMount() {}
380
- UNSAFE_componentWillUpdate() {}
381
- UNSAFE_componentWillReceiveProps() {}
382
- render() {
383
- return (
384
- <React.unstable_ConcurrentMode>
385
- <AsyncRoot />
386
- </React.unstable_ConcurrentMode>
387
- );
388
- }
389
- }
370
class AsyncRoot extends React.Component {
371
UNSAFE_componentWillMount() {}
372
UNSAFE_componentWillUpdate() {}
@@ -409,15 +389,14 @@ describe('ReactStrictMode', () => {
389
}
390
}
391
412
- let rendered;
392
+ const root = ReactTestRenderer.create(null, {
393
+ unstable_isConcurrent: true,
394
+ });
395
+ root.update(<AsyncRoot />);
396
397
expect(() => {
415
- expect(
416
- () => (rendered = ReactTestRenderer.create(<SyncRoot />)),
417
- ).toWarnDev(
398
+ expect(() => Scheduler.flushAll()).toWarnDev(
399
'Unsafe lifecycle methods were found within a strict-mode tree:' +
419
- '\n in ConcurrentMode (at **)' +
420
- '\n in SyncRoot (at **)' +
400
'\n\ncomponentWillMount: Please update the following components ' +
401
'to use componentDidMount instead: AsyncRoot, Parent' +
402
'\n\ncomponentWillReceiveProps: Please update the following components ' +
@@ -426,6 +405,7 @@ describe('ReactStrictMode', () => {
405
'to use componentDidUpdate instead: AsyncRoot, Parent' +
406
'\n\nLearn more about this warning here:' +
407
'\nhttps://fb.me/react-strict-mode-warnings',
408
+ {withoutStack: true},
409
);
410
}).toLowPriorityWarnDev(
411
[
@@ -437,103 +417,14 @@ describe('ReactStrictMode', () => {
417
);
418
419
// Dedupe
440
- rendered = ReactTestRenderer.create(<SyncRoot />);
441
- rendered.update(<SyncRoot />);
442
- });
443
-
444
- it('should group warnings by async root', () => {
445
- class SyncRoot extends React.Component {
446
- UNSAFE_componentWillMount() {}
447
- UNSAFE_componentWillUpdate() {}
448
- UNSAFE_componentWillReceiveProps() {}
449
- render() {
450
- return (
451
- <div>
452
- <AsyncRootOne />
453
- <AsyncRootTwo />
454
- </div>
455
- );
456
- }
457
- }
458
- class AsyncRootOne extends React.Component {
459
- render() {
460
- return (
461
- <React.unstable_ConcurrentMode>
462
- <Foo>
463
- <Bar />
464
- </Foo>
465
- </React.unstable_ConcurrentMode>
466
- );
467
- }
468
- }
469
- class AsyncRootTwo extends React.Component {
470
- render() {
471
- return (
472
- <React.unstable_ConcurrentMode>
473
- <Foo>
474
- <Baz />
475
- </Foo>
476
- </React.unstable_ConcurrentMode>
477
- );
478
- }
479
- }
480
- class Foo extends React.Component {
481
- componentWillMount() {}
482
- render() {
483
- return this.props.children;
484
- }
485
- }
486
- class Bar extends React.Component {
487
- componentWillMount() {}
488
- render() {
489
- return null;
490
- }
491
- }
492
- class Baz extends React.Component {
493
- componentWillMount() {}
494
- render() {
495
- return null;
496
- }
497
- }
498
-
499
- let rendered;
500
-
501
- expect(() => {
502
- expect(
503
- () => (rendered = ReactTestRenderer.create(<SyncRoot />)),
504
- ).toWarnDev([
505
- 'Unsafe lifecycle methods were found within a strict-mode tree:' +
506
- '\n in ConcurrentMode (at **)' +
507
- '\n in AsyncRootOne (at **)' +
508
- '\n in div (at **)' +
509
- '\n in SyncRoot (at **)' +
510
- '\n\ncomponentWillMount: Please update the following components ' +
511
- 'to use componentDidMount instead: Bar, Foo',
512
- 'Unsafe lifecycle methods were found within a strict-mode tree:' +
513
- '\n in ConcurrentMode (at **)' +
514
- '\n in AsyncRootTwo (at **)' +
515
- '\n in div (at **)' +
516
- '\n in SyncRoot (at **)' +
517
- '\n\ncomponentWillMount: Please update the following components ' +
518
- 'to use componentDidMount instead: Baz',
519
- ]);
520
- }).toLowPriorityWarnDev(['componentWillMount is deprecated'], {
521
- withoutStack: true,
522
- });
523
-
524
- // Dedupe
525
- rendered = ReactTestRenderer.create(<SyncRoot />);
526
- rendered.update(<SyncRoot />);
420
+ root.update(<AsyncRoot />);
421
+ Scheduler.flushAll();
422
});
423
424
it('should warn about components not present during the initial render', () => {
425
class AsyncRoot extends React.Component {
426
render() {
532
- return (
533
- <React.unstable_ConcurrentMode>
534
- {this.props.foo ? <Foo /> : <Bar />}
535
- </React.unstable_ConcurrentMode>
536
- );
427
+ return this.props.foo ? <Foo /> : <Bar />;
428
}
429
}
430
class Foo extends React.Component {
@@ -549,32 +440,34 @@ describe('ReactStrictMode', () => {
440
}
441
}
442
552
- let rendered;
553
- expect(() => {
554
- rendered = ReactTestRenderer.create(<AsyncRoot foo={true} />);
555
- }).toWarnDev(
443
+ const root = ReactTestRenderer.create(null, {
444
+ unstable_isConcurrent: true,
445
+ });
446
+ root.update(<AsyncRoot foo={true} />);
447
+ expect(() => Scheduler.flushAll()).toWarnDev(
448
'Unsafe lifecycle methods were found within a strict-mode tree:' +
557
- '\n in ConcurrentMode (at **)' +
558
- '\n in AsyncRoot (at **)' +
449
'\n\ncomponentWillMount: Please update the following components ' +
450
'to use componentDidMount instead: Foo' +
451
'\n\nLearn more about this warning here:' +
452
'\nhttps://fb.me/react-strict-mode-warnings',
453
+ {withoutStack: true},
454
);
455
565
- expect(() => rendered.update(<AsyncRoot foo={false} />)).toWarnDev(
456
+ root.update(<AsyncRoot foo={false} />);
457
+ expect(() => Scheduler.flushAll()).toWarnDev(
458
'Unsafe lifecycle methods were found within a strict-mode tree:' +
567
- '\n in ConcurrentMode (at **)' +
568
- '\n in AsyncRoot (at **)' +
459
'\n\ncomponentWillMount: Please update the following components ' +
460
'to use componentDidMount instead: Bar' +
461
'\n\nLearn more about this warning here:' +
462
'\nhttps://fb.me/react-strict-mode-warnings',
463
+ {withoutStack: true},
464
);
465
466
// Dedupe
576
- rendered.update(<AsyncRoot foo={true} />);
577
- rendered.update(<AsyncRoot foo={false} />);
467
+ root.update(<AsyncRoot foo={true} />);
468
+ Scheduler.flushAll();
469
+ root.update(<AsyncRoot foo={false} />);
470
+ Scheduler.flushAll();
471
});
472
473
it('should also warn inside of "strict" mode trees', () => {
packages/shared/ReactSymbols.js
+2
@@ -32,6 +32,8 @@ export const REACT_PROVIDER_TYPE = hasSymbol
32
export const REACT_CONTEXT_TYPE = hasSymbol
33
? Symbol.for('react.context')
34
: 0xeace;
35
+// TODO: We don't use AsyncMode or ConcurrentMode anymore. They were temporary
36
+// (unstable) APIs that have been removed. Can we remove the symbols?
37
export const REACT_ASYNC_MODE_TYPE = hasSymbol
38
? Symbol.for('react.async_mode')
39
: 0xeacf;
packages/shared/getComponentName.js
-3
@@ -11,7 +11,6 @@ import type {LazyComponent} from 'shared/ReactLazyComponent';
11
12
import warningWithoutStack from 'shared/warningWithoutStack';
13
import {
14
- REACT_CONCURRENT_MODE_TYPE,
14
REACT_CONTEXT_TYPE,
15
REACT_FORWARD_REF_TYPE,
16
REACT_FRAGMENT_TYPE,
@@ -64,8 +63,6 @@ function getComponentName(type: mixed): string | null {
63
return type;
64
}
65
switch (type) {
67
- case REACT_CONCURRENT_MODE_TYPE:
68
- return 'ConcurrentMode';
66
case REACT_FRAGMENT_TYPE:
67
return 'Fragment';
68
case REACT_PORTAL_TYPE: