Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Remove blocking mode and blocking root" #20916

Merged
merged 1 commit into from
Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/react-dom/index.classic.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export {
unmountComponentAtNode,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
Expand Down
1 change: 1 addition & 0 deletions packages/react-dom/index.experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot as unstable_createRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
// DO NOT USE: Temporarily exposing this to migrate off of Scheduler.runWithPriority.
Expand Down
2 changes: 2 additions & 0 deletions packages/react-dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export {
unmountComponentAtNode,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-dom/index.modern.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export {
version,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_runWithPriority,
Expand Down
27 changes: 27 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,33 @@ describe('ReactDOMFiberAsync', () => {
expect(containerC.textContent).toEqual('Finished');
});

describe('createBlockingRoot', () => {
// @gate experimental
it('updates flush without yielding in the next event', () => {
const root = ReactDOM.unstable_createBlockingRoot(container);

function Text(props) {
Scheduler.unstable_yieldValue(props.text);
return props.text;
}

root.render(
<>
<Text text="A" />
<Text text="B" />
<Text text="C" />
</>,
);

// Nothing should have rendered yet
expect(container.textContent).toEqual('');

// Everything should render immediately in the next event
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
expect(container.textContent).toEqual('ABC');
});
});

// @gate experimental
it('unmounted roots should never clear newer root content from a container', () => {
const ref = React.createRef();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ describe('ReactDOMServerPartialHydration', () => {
}).toErrorDev(
'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
'ReactDOM.hydrate(element, container) to ' +
'ReactDOM.createRoot(container, { hydrate: true })' +
'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
'.render(element) or remove the Suspense components from the server ' +
'rendered components.' +
'\n in Suspense (at **)' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('ReactDOMServerSuspense', () => {
expect(divB.textContent).toBe('B');

act(() => {
const root = ReactDOM.createRoot(parent, {hydrate: true});
const root = ReactDOM.createBlockingRoot(parent, {hydrate: true});
root.render(example);
});

Expand Down
58 changes: 52 additions & 6 deletions packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,33 @@ describe('ReactTestUtils.act()', () => {

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

// and then in blocking mode
if (__EXPERIMENTAL__) {
let blockingRoot = null;
const renderBatched = (el, dom) => {
blockingRoot = ReactDOM.unstable_createBlockingRoot(dom);
blockingRoot.render(el);
};

const unmountBatched = dom => {
if (blockingRoot !== null) {
blockingRoot.unmount();
blockingRoot = null;
}
};

const rerenderBatched = el => {
blockingRoot.render(el);
};

runActTests(
'blocking mode',
renderBatched,
unmountBatched,
rerenderBatched,
);
}

describe('unacted effects', () => {
function App() {
React.useEffect(() => {}, []);
Expand All @@ -97,6 +124,19 @@ describe('ReactTestUtils.act()', () => {
]);
});

// @gate experimental
it('warns in blocking mode', () => {
expect(() => {
const root = ReactDOM.unstable_createBlockingRoot(
document.createElement('div'),
);
root.render(<App />);
Scheduler.unstable_flushAll();
}).toErrorDev([
'An update to App ran an effect, but was not wrapped in act(...)',
]);
});

// @gate experimental
it('warns in concurrent mode', () => {
expect(() => {
Expand Down Expand Up @@ -691,10 +731,14 @@ function runActTests(label, render, unmount, rerender) {

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

Expand Down Expand Up @@ -750,8 +794,10 @@ function runActTests(label, render, unmount, rerender) {
// In Concurrent Mode, refresh transitions delay indefinitely.
expect(document.querySelector('[data-test-id=spinner]')).toBeNull();
} else {
// In Legacy Mode, all fallbacks are forced to display,
// even during a refresh transition.
// In Legacy Mode and Blocking Mode, all fallbacks are forced to
// display, even during a refresh transition.
// TODO: Consider delaying indefinitely in Blocking Mode, to match
// Concurrent Mode semantics.
expect(
document.querySelector('[data-test-id=spinner]'),
).not.toBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,22 @@ it('should warn when rendering in concurrent mode', () => {
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
}).toErrorDev([]);
});

// @gate experimental
it('should warn when rendering in blocking mode', () => {
expect(() => {
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev(
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
'to guarantee consistent behaviour across tests and browsers.',
{withoutStack: true},
);
// does not warn twice
expect(() => {
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev([]);
});
3 changes: 2 additions & 1 deletion packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './ReactDOMLegacy';
import {createRoot, isValidContainer} from './ReactDOMRoot';
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
import {createEventHandle} from './ReactDOMEventHandle';

import {
Expand Down Expand Up @@ -201,6 +201,7 @@ export {
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
createBlockingRoot,
flushControlled as unstable_flushControlled,
scheduleHydration as unstable_scheduleHydration,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
Expand Down
32 changes: 26 additions & 6 deletions packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,25 @@ import {
registerMutableSourceForHydration,
} from 'react-reconciler/src/ReactFiberReconciler';
import invariant from 'shared/invariant';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {
BlockingRoot,
ConcurrentRoot,
LegacyRoot,
} from 'react-reconciler/src/ReactRootTags';

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

function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, LegacyRoot, options);
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
this._internalRoot = createRootImpl(container, tag, options);
}

ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
children: ReactNodeList,
): void {
const root = this._internalRoot;
Expand Down Expand Up @@ -91,7 +99,7 @@ ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
updateContainer(children, root, null, null);
};

ReactDOMRoot.prototype.unmount = ReactDOMLegacyRoot.prototype.unmount = function(): void {
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {
if (__DEV__) {
if (typeof arguments[0] === 'function') {
console.error(
Expand Down Expand Up @@ -161,11 +169,23 @@ export function createRoot(
return new ReactDOMRoot(container, options);
}

export function createBlockingRoot(
container: Container,
options?: RootOptions,
): RootType {
invariant(
isValidContainer(container),
'createRoot(...): Target container is not a DOM element.',
);
warnIfReactDOMContainerInDEV(container);
return new ReactDOMBlockingRoot(container, BlockingRoot, options);
}

export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
return new ReactDOMLegacyRoot(container, options);
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

export function isValidContainer(node: mixed): boolean {
Expand Down
1 change: 1 addition & 0 deletions packages/react-noop-renderer/src/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const {
getPendingChildren,
getOrCreateRootContainer,
createRoot,
createBlockingRoot,
createLegacyRoot,
getChildrenAsJSX,
getPendingChildrenAsJSX,
Expand Down
1 change: 1 addition & 0 deletions packages/react-noop-renderer/src/ReactNoopPersistent.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const {
getPendingChildren,
getOrCreateRootContainer,
createRoot,
createBlockingRoot,
createLegacyRoot,
getChildrenAsJSX,
getPendingChildrenAsJSX,
Expand Down
33 changes: 32 additions & 1 deletion packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import type {RootTag} from 'react-reconciler/src/ReactRootTags';

import * as Scheduler from 'scheduler/unstable_mock';
import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {
ConcurrentRoot,
BlockingRoot,
LegacyRoot,
} from 'react-reconciler/src/ReactRootTags';

import {
enableNativeEventPriorityInference,
Expand Down Expand Up @@ -752,6 +756,33 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
};
},

createBlockingRoot() {
const container = {
rootID: '' + idCounter++,
pendingChildren: [],
children: [],
};
const fiberRoot = NoopRenderer.createContainer(
container,
BlockingRoot,
false,
null,
null,
);
return {
_Scheduler: Scheduler,
render(children: ReactNodeList) {
NoopRenderer.updateContainer(children, fiberRoot, null, null);
},
getChildren() {
return getChildren(container);
},
getChildrenAsJSX() {
return getChildrenAsJSX(container);
},
};
},

createLegacyRoot() {
const container = {
rootID: '' + idCounter++,
Expand Down
23 changes: 21 additions & 2 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
enableScopeAPI,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
import {
IndeterminateComponent,
ClassComponent,
Expand Down Expand Up @@ -68,6 +68,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
BlockingMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
Expand Down Expand Up @@ -426,7 +427,25 @@ export function createHostRootFiber(
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode;
mode = ConcurrentMode | BlockingMode;
if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (strictModeLevelOverride >= 2) {
mode |= StrictEffectsMode;
}
}
} else {
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
} else {
mode |= StrictLegacyMode;
}
}
} else if (tag === BlockingRoot) {
mode = BlockingMode;
if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
Expand Down
Loading