Skip to content

Commit ee43263

Browse files
authored
Revert "Remove blocking mode and blocking root (#20888)" (#20916)
This reverts commit 553440b.
1 parent de0ee76 commit ee43263

36 files changed

+531
-81
lines changed

packages/react-dom/index.classic.fb.js

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export {
3030
unmountComponentAtNode,
3131
createRoot,
3232
createRoot as unstable_createRoot,
33+
createBlockingRoot,
34+
createBlockingRoot as unstable_createBlockingRoot,
3335
unstable_flushControlled,
3436
unstable_scheduleHydration,
3537
unstable_runWithPriority,

packages/react-dom/index.experimental.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
unmountComponentAtNode,
2121
// exposeConcurrentModeAPIs
2222
createRoot as unstable_createRoot,
23+
createBlockingRoot as unstable_createBlockingRoot,
2324
unstable_flushControlled,
2425
unstable_scheduleHydration,
2526
// DO NOT USE: Temporarily exposing this to migrate off of Scheduler.runWithPriority.

packages/react-dom/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export {
2121
unmountComponentAtNode,
2222
createRoot,
2323
createRoot as unstable_createRoot,
24+
createBlockingRoot,
25+
createBlockingRoot as unstable_createBlockingRoot,
2426
unstable_flushControlled,
2527
unstable_scheduleHydration,
2628
unstable_runWithPriority,

packages/react-dom/index.modern.fb.js

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export {
1515
version,
1616
createRoot,
1717
createRoot as unstable_createRoot,
18+
createBlockingRoot,
19+
createBlockingRoot as unstable_createBlockingRoot,
1820
unstable_flushControlled,
1921
unstable_scheduleHydration,
2022
unstable_runWithPriority,

packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js

+27
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,33 @@ describe('ReactDOMFiberAsync', () => {
593593
expect(containerC.textContent).toEqual('Finished');
594594
});
595595

596+
describe('createBlockingRoot', () => {
597+
// @gate experimental
598+
it('updates flush without yielding in the next event', () => {
599+
const root = ReactDOM.unstable_createBlockingRoot(container);
600+
601+
function Text(props) {
602+
Scheduler.unstable_yieldValue(props.text);
603+
return props.text;
604+
}
605+
606+
root.render(
607+
<>
608+
<Text text="A" />
609+
<Text text="B" />
610+
<Text text="C" />
611+
</>,
612+
);
613+
614+
// Nothing should have rendered yet
615+
expect(container.textContent).toEqual('');
616+
617+
// Everything should render immediately in the next event
618+
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
619+
expect(container.textContent).toEqual('ABC');
620+
});
621+
});
622+
596623
// @gate experimental
597624
it('unmounted roots should never clear newer root content from a container', () => {
598625
const ref = React.createRef();

packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe('ReactDOMServerPartialHydration', () => {
352352
}).toErrorDev(
353353
'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
354354
'ReactDOM.hydrate(element, container) to ' +
355-
'ReactDOM.createRoot(container, { hydrate: true })' +
355+
'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
356356
'.render(element) or remove the Suspense components from the server ' +
357357
'rendered components.' +
358358
'\n in Suspense (at **)' +

packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ describe('ReactDOMServerSuspense', () => {
127127
expect(divB.textContent).toBe('B');
128128

129129
act(() => {
130-
const root = ReactDOM.createRoot(parent, {hydrate: true});
130+
const root = ReactDOM.createBlockingRoot(parent, {hydrate: true});
131131
root.render(example);
132132
});
133133

packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js

+52-6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ describe('ReactTestUtils.act()', () => {
7272

7373
runActTests('legacy mode', renderLegacy, unmountLegacy, rerenderLegacy);
7474

75+
// and then in blocking mode
76+
if (__EXPERIMENTAL__) {
77+
let blockingRoot = null;
78+
const renderBatched = (el, dom) => {
79+
blockingRoot = ReactDOM.unstable_createBlockingRoot(dom);
80+
blockingRoot.render(el);
81+
};
82+
83+
const unmountBatched = dom => {
84+
if (blockingRoot !== null) {
85+
blockingRoot.unmount();
86+
blockingRoot = null;
87+
}
88+
};
89+
90+
const rerenderBatched = el => {
91+
blockingRoot.render(el);
92+
};
93+
94+
runActTests(
95+
'blocking mode',
96+
renderBatched,
97+
unmountBatched,
98+
rerenderBatched,
99+
);
100+
}
101+
75102
describe('unacted effects', () => {
76103
function App() {
77104
React.useEffect(() => {}, []);
@@ -97,6 +124,19 @@ describe('ReactTestUtils.act()', () => {
97124
]);
98125
});
99126

127+
// @gate experimental
128+
it('warns in blocking mode', () => {
129+
expect(() => {
130+
const root = ReactDOM.unstable_createBlockingRoot(
131+
document.createElement('div'),
132+
);
133+
root.render(<App />);
134+
Scheduler.unstable_flushAll();
135+
}).toErrorDev([
136+
'An update to App ran an effect, but was not wrapped in act(...)',
137+
]);
138+
});
139+
100140
// @gate experimental
101141
it('warns in concurrent mode', () => {
102142
expect(() => {
@@ -691,10 +731,14 @@ function runActTests(label, render, unmount, rerender) {
691731

692732
it('triggers fallbacks if available', async () => {
693733
if (label !== 'legacy mode') {
694-
// FIXME: Support for Concurrent Root intentionally removed
695-
// from the public version of `act`. It will be added back in
696-
// a future major version, Concurrent Root officially released.
697-
// Consider skipping all non-Legacy tests in this suite until then.
734+
// FIXME: Support for Blocking* and Concurrent Mode were
735+
// intentionally removed from the public version of `act`. It will
736+
// be added back in a future major version, before Blocking and and
737+
// Concurrent Mode are officially released. Consider disabling all
738+
// non-Legacy tests in this suite until then.
739+
//
740+
// *Blocking Mode actually does happen to work, though
741+
// not "officially" since it's an unreleased feature.
698742
return;
699743
}
700744

@@ -750,8 +794,10 @@ function runActTests(label, render, unmount, rerender) {
750794
// In Concurrent Mode, refresh transitions delay indefinitely.
751795
expect(document.querySelector('[data-test-id=spinner]')).toBeNull();
752796
} else {
753-
// In Legacy Mode, all fallbacks are forced to display,
754-
// even during a refresh transition.
797+
// In Legacy Mode and Blocking Mode, all fallbacks are forced to
798+
// display, even during a refresh transition.
799+
// TODO: Consider delaying indefinitely in Blocking Mode, to match
800+
// Concurrent Mode semantics.
755801
expect(
756802
document.querySelector('[data-test-id=spinner]'),
757803
).not.toBeNull();

packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js

+19
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,22 @@ it('should warn when rendering in concurrent mode', () => {
4343
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
4444
}).toErrorDev([]);
4545
});
46+
47+
// @gate experimental
48+
it('should warn when rendering in blocking mode', () => {
49+
expect(() => {
50+
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
51+
<App />,
52+
);
53+
}).toErrorDev(
54+
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
55+
'to guarantee consistent behaviour across tests and browsers.',
56+
{withoutStack: true},
57+
);
58+
// does not warn twice
59+
expect(() => {
60+
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
61+
<App />,
62+
);
63+
}).toErrorDev([]);
64+
});

packages/react-dom/src/client/ReactDOM.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
unstable_renderSubtreeIntoContainer,
1919
unmountComponentAtNode,
2020
} from './ReactDOMLegacy';
21-
import {createRoot, isValidContainer} from './ReactDOMRoot';
21+
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
2222
import {createEventHandle} from './ReactDOMEventHandle';
2323

2424
import {
@@ -201,6 +201,7 @@ export {
201201
unmountComponentAtNode,
202202
// exposeConcurrentModeAPIs
203203
createRoot,
204+
createBlockingRoot,
204205
flushControlled as unstable_flushControlled,
205206
scheduleHydration as unstable_scheduleHydration,
206207
// Disabled behind disableUnstableRenderSubtreeIntoContainer

packages/react-dom/src/client/ReactDOMRoot.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,25 @@ import {
5151
registerMutableSourceForHydration,
5252
} from 'react-reconciler/src/ReactFiberReconciler';
5353
import invariant from 'shared/invariant';
54-
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
54+
import {
55+
BlockingRoot,
56+
ConcurrentRoot,
57+
LegacyRoot,
58+
} from 'react-reconciler/src/ReactRootTags';
5559

5660
function ReactDOMRoot(container: Container, options: void | RootOptions) {
5761
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
5862
}
5963

60-
function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) {
61-
this._internalRoot = createRootImpl(container, LegacyRoot, options);
64+
function ReactDOMBlockingRoot(
65+
container: Container,
66+
tag: RootTag,
67+
options: void | RootOptions,
68+
) {
69+
this._internalRoot = createRootImpl(container, tag, options);
6270
}
6371

64-
ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
72+
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
6573
children: ReactNodeList,
6674
): void {
6775
const root = this._internalRoot;
@@ -91,7 +99,7 @@ ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
9199
updateContainer(children, root, null, null);
92100
};
93101

94-
ReactDOMRoot.prototype.unmount = ReactDOMLegacyRoot.prototype.unmount = function(): void {
102+
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {
95103
if (__DEV__) {
96104
if (typeof arguments[0] === 'function') {
97105
console.error(
@@ -161,11 +169,23 @@ export function createRoot(
161169
return new ReactDOMRoot(container, options);
162170
}
163171

172+
export function createBlockingRoot(
173+
container: Container,
174+
options?: RootOptions,
175+
): RootType {
176+
invariant(
177+
isValidContainer(container),
178+
'createRoot(...): Target container is not a DOM element.',
179+
);
180+
warnIfReactDOMContainerInDEV(container);
181+
return new ReactDOMBlockingRoot(container, BlockingRoot, options);
182+
}
183+
164184
export function createLegacyRoot(
165185
container: Container,
166186
options?: RootOptions,
167187
): RootType {
168-
return new ReactDOMLegacyRoot(container, options);
188+
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
169189
}
170190

171191
export function isValidContainer(node: mixed): boolean {

packages/react-noop-renderer/src/ReactNoop.js

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const {
2323
getPendingChildren,
2424
getOrCreateRootContainer,
2525
createRoot,
26+
createBlockingRoot,
2627
createLegacyRoot,
2728
getChildrenAsJSX,
2829
getPendingChildrenAsJSX,

packages/react-noop-renderer/src/ReactNoopPersistent.js

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const {
2323
getPendingChildren,
2424
getOrCreateRootContainer,
2525
createRoot,
26+
createBlockingRoot,
2627
createLegacyRoot,
2728
getChildrenAsJSX,
2829
getPendingChildrenAsJSX,

packages/react-noop-renderer/src/createReactNoop.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import type {RootTag} from 'react-reconciler/src/ReactRootTags';
2121

2222
import * as Scheduler from 'scheduler/unstable_mock';
2323
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
24-
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
24+
import {
25+
ConcurrentRoot,
26+
BlockingRoot,
27+
LegacyRoot,
28+
} from 'react-reconciler/src/ReactRootTags';
2529

2630
import {
2731
enableNativeEventPriorityInference,
@@ -752,6 +756,33 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
752756
};
753757
},
754758

759+
createBlockingRoot() {
760+
const container = {
761+
rootID: '' + idCounter++,
762+
pendingChildren: [],
763+
children: [],
764+
};
765+
const fiberRoot = NoopRenderer.createContainer(
766+
container,
767+
BlockingRoot,
768+
false,
769+
null,
770+
null,
771+
);
772+
return {
773+
_Scheduler: Scheduler,
774+
render(children: ReactNodeList) {
775+
NoopRenderer.updateContainer(children, fiberRoot, null, null);
776+
},
777+
getChildren() {
778+
return getChildren(container);
779+
},
780+
getChildrenAsJSX() {
781+
return getChildrenAsJSX(container);
782+
},
783+
};
784+
},
785+
755786
createLegacyRoot() {
756787
const container = {
757788
rootID: '' + idCounter++,

packages/react-reconciler/src/ReactFiber.new.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
enableScopeAPI,
2727
} from 'shared/ReactFeatureFlags';
2828
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
29-
import {ConcurrentRoot} from './ReactRootTags';
29+
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
3030
import {
3131
IndeterminateComponent,
3232
ClassComponent,
@@ -68,6 +68,7 @@ import {
6868
ProfileMode,
6969
StrictLegacyMode,
7070
StrictEffectsMode,
71+
BlockingMode,
7172
} from './ReactTypeOfMode';
7273
import {
7374
REACT_FORWARD_REF_TYPE,
@@ -426,7 +427,25 @@ export function createHostRootFiber(
426427
): Fiber {
427428
let mode;
428429
if (tag === ConcurrentRoot) {
429-
mode = ConcurrentMode;
430+
mode = ConcurrentMode | BlockingMode;
431+
if (strictModeLevelOverride !== null) {
432+
if (strictModeLevelOverride >= 1) {
433+
mode |= StrictLegacyMode;
434+
}
435+
if (enableStrictEffects) {
436+
if (strictModeLevelOverride >= 2) {
437+
mode |= StrictEffectsMode;
438+
}
439+
}
440+
} else {
441+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
442+
mode |= StrictLegacyMode | StrictEffectsMode;
443+
} else {
444+
mode |= StrictLegacyMode;
445+
}
446+
}
447+
} else if (tag === BlockingRoot) {
448+
mode = BlockingMode;
430449
if (strictModeLevelOverride !== null) {
431450
if (strictModeLevelOverride >= 1) {
432451
mode |= StrictLegacyMode;

0 commit comments

Comments
 (0)