mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Remove blocking mode and blocking root (#20888)
* Remove blocking mode and blocking root * Add back SuspenseList test * Clean up ReactDOMLegacyRoot * Remove dupe ConcurrentRoot * Update comment
This commit is contained in:
@@ -30,8 +30,6 @@ export {
|
||||
unmountComponentAtNode,
|
||||
createRoot,
|
||||
createRoot as unstable_createRoot,
|
||||
createBlockingRoot,
|
||||
createBlockingRoot as unstable_createBlockingRoot,
|
||||
unstable_flushControlled,
|
||||
unstable_scheduleHydration,
|
||||
unstable_runWithPriority,
|
||||
|
||||
@@ -20,7 +20,6 @@ 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.
|
||||
|
||||
Vendored
-2
@@ -21,8 +21,6 @@ export {
|
||||
unmountComponentAtNode,
|
||||
createRoot,
|
||||
createRoot as unstable_createRoot,
|
||||
createBlockingRoot,
|
||||
createBlockingRoot as unstable_createBlockingRoot,
|
||||
unstable_flushControlled,
|
||||
unstable_scheduleHydration,
|
||||
unstable_runWithPriority,
|
||||
|
||||
@@ -15,8 +15,6 @@ export {
|
||||
version,
|
||||
createRoot,
|
||||
createRoot as unstable_createRoot,
|
||||
createBlockingRoot,
|
||||
createBlockingRoot as unstable_createBlockingRoot,
|
||||
unstable_flushControlled,
|
||||
unstable_scheduleHydration,
|
||||
unstable_runWithPriority,
|
||||
|
||||
@@ -593,33 +593,6 @@ 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();
|
||||
|
||||
@@ -352,7 +352,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
}).toErrorDev(
|
||||
'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
|
||||
'ReactDOM.hydrate(element, container) to ' +
|
||||
'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
|
||||
'ReactDOM.createRoot(container, { hydrate: true })' +
|
||||
'.render(element) or remove the Suspense components from the server ' +
|
||||
'rendered components.' +
|
||||
'\n in Suspense (at **)' +
|
||||
|
||||
@@ -127,7 +127,7 @@ describe('ReactDOMServerSuspense', () => {
|
||||
expect(divB.textContent).toBe('B');
|
||||
|
||||
act(() => {
|
||||
const root = ReactDOM.createBlockingRoot(parent, {hydrate: true});
|
||||
const root = ReactDOM.createRoot(parent, {hydrate: true});
|
||||
root.render(example);
|
||||
});
|
||||
|
||||
|
||||
+6
-52
@@ -72,33 +72,6 @@ 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(() => {}, []);
|
||||
@@ -124,19 +97,6 @@ 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(() => {
|
||||
@@ -731,14 +691,10 @@ function runActTests(label, render, unmount, rerender) {
|
||||
|
||||
it('triggers fallbacks if available', async () => {
|
||||
if (label !== 'legacy mode') {
|
||||
// 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.
|
||||
// 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.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -794,10 +750,8 @@ 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 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.
|
||||
// In Legacy Mode, all fallbacks are forced to display,
|
||||
// even during a refresh transition.
|
||||
expect(
|
||||
document.querySelector('[data-test-id=spinner]'),
|
||||
).not.toBeNull();
|
||||
|
||||
@@ -43,22 +43,3 @@ 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([]);
|
||||
});
|
||||
|
||||
+1
-2
@@ -18,7 +18,7 @@ import {
|
||||
unstable_renderSubtreeIntoContainer,
|
||||
unmountComponentAtNode,
|
||||
} from './ReactDOMLegacy';
|
||||
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';
|
||||
import {createRoot, isValidContainer} from './ReactDOMRoot';
|
||||
import {createEventHandle} from './ReactDOMEventHandle';
|
||||
|
||||
import {
|
||||
@@ -201,7 +201,6 @@ export {
|
||||
unmountComponentAtNode,
|
||||
// exposeConcurrentModeAPIs
|
||||
createRoot,
|
||||
createBlockingRoot,
|
||||
flushControlled as unstable_flushControlled,
|
||||
scheduleHydration as unstable_scheduleHydration,
|
||||
// Disabled behind disableUnstableRenderSubtreeIntoContainer
|
||||
|
||||
+6
-26
@@ -51,25 +51,17 @@ import {
|
||||
registerMutableSourceForHydration,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
import invariant from 'shared/invariant';
|
||||
import {
|
||||
BlockingRoot,
|
||||
ConcurrentRoot,
|
||||
LegacyRoot,
|
||||
} from 'react-reconciler/src/ReactRootTags';
|
||||
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
|
||||
|
||||
function ReactDOMRoot(container: Container, options: void | RootOptions) {
|
||||
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
|
||||
}
|
||||
|
||||
function ReactDOMBlockingRoot(
|
||||
container: Container,
|
||||
tag: RootTag,
|
||||
options: void | RootOptions,
|
||||
) {
|
||||
this._internalRoot = createRootImpl(container, tag, options);
|
||||
function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) {
|
||||
this._internalRoot = createRootImpl(container, LegacyRoot, options);
|
||||
}
|
||||
|
||||
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
|
||||
ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
|
||||
children: ReactNodeList,
|
||||
): void {
|
||||
const root = this._internalRoot;
|
||||
@@ -99,7 +91,7 @@ ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function
|
||||
updateContainer(children, root, null, null);
|
||||
};
|
||||
|
||||
ReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount = function(): void {
|
||||
ReactDOMRoot.prototype.unmount = ReactDOMLegacyRoot.prototype.unmount = function(): void {
|
||||
if (__DEV__) {
|
||||
if (typeof arguments[0] === 'function') {
|
||||
console.error(
|
||||
@@ -169,23 +161,11 @@ 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 ReactDOMBlockingRoot(container, LegacyRoot, options);
|
||||
return new ReactDOMLegacyRoot(container, options);
|
||||
}
|
||||
|
||||
export function isValidContainer(node: mixed): boolean {
|
||||
|
||||
@@ -23,7 +23,6 @@ export const {
|
||||
getPendingChildren,
|
||||
getOrCreateRootContainer,
|
||||
createRoot,
|
||||
createBlockingRoot,
|
||||
createLegacyRoot,
|
||||
getChildrenAsJSX,
|
||||
getPendingChildrenAsJSX,
|
||||
|
||||
@@ -23,7 +23,6 @@ export const {
|
||||
getPendingChildren,
|
||||
getOrCreateRootContainer,
|
||||
createRoot,
|
||||
createBlockingRoot,
|
||||
createLegacyRoot,
|
||||
getChildrenAsJSX,
|
||||
getPendingChildrenAsJSX,
|
||||
|
||||
+1
-32
@@ -21,11 +21,7 @@ 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,
|
||||
BlockingRoot,
|
||||
LegacyRoot,
|
||||
} from 'react-reconciler/src/ReactRootTags';
|
||||
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
|
||||
|
||||
import {
|
||||
enableNativeEventPriorityInference,
|
||||
@@ -756,33 +752,6 @@ 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++,
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
|
||||
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
|
||||
import {ConcurrentRoot} from './ReactRootTags';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
@@ -68,7 +68,6 @@ import {
|
||||
ProfileMode,
|
||||
StrictLegacyMode,
|
||||
StrictEffectsMode,
|
||||
BlockingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
@@ -427,25 +426,7 @@ export function createHostRootFiber(
|
||||
): Fiber {
|
||||
let mode;
|
||||
if (tag === ConcurrentRoot) {
|
||||
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;
|
||||
mode = ConcurrentMode;
|
||||
if (strictModeLevelOverride !== null) {
|
||||
if (strictModeLevelOverride >= 1) {
|
||||
mode |= StrictLegacyMode;
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
|
||||
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
|
||||
import {ConcurrentRoot} from './ReactRootTags';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
ClassComponent,
|
||||
@@ -68,7 +68,6 @@ import {
|
||||
ProfileMode,
|
||||
StrictLegacyMode,
|
||||
StrictEffectsMode,
|
||||
BlockingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
@@ -427,25 +426,7 @@ export function createHostRootFiber(
|
||||
): Fiber {
|
||||
let mode;
|
||||
if (tag === ConcurrentRoot) {
|
||||
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;
|
||||
mode = ConcurrentMode;
|
||||
if (strictModeLevelOverride !== null) {
|
||||
if (strictModeLevelOverride >= 1) {
|
||||
mode |= StrictLegacyMode;
|
||||
|
||||
@@ -126,7 +126,6 @@ import {
|
||||
NoMode,
|
||||
ProfileMode,
|
||||
StrictLegacyMode,
|
||||
BlockingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
shouldSetTextContent,
|
||||
@@ -604,7 +603,6 @@ function updateOffscreenComponent(
|
||||
// Rendering a hidden tree.
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
// In legacy sync mode, don't defer the subtree. Render it now.
|
||||
// TODO: Figure out what we should do in Blocking mode.
|
||||
const nextState: OffscreenState = {
|
||||
baseLanes: NoLanes,
|
||||
cachePool: null,
|
||||
@@ -2117,7 +2115,10 @@ function mountSuspenseFallbackChildren(
|
||||
|
||||
let primaryChildFragment;
|
||||
let fallbackChildFragment;
|
||||
if ((mode & BlockingMode) === NoMode && progressedPrimaryFragment !== null) {
|
||||
if (
|
||||
(mode & ConcurrentMode) === NoMode &&
|
||||
progressedPrimaryFragment !== null
|
||||
) {
|
||||
// In legacy mode, we commit the primary tree as if it successfully
|
||||
// completed, even though it's in an inconsistent state.
|
||||
primaryChildFragment = progressedPrimaryFragment;
|
||||
@@ -2189,7 +2190,7 @@ function updateSuspensePrimaryChildren(
|
||||
children: primaryChildren,
|
||||
},
|
||||
);
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
primaryChildFragment.lanes = renderLanes;
|
||||
}
|
||||
primaryChildFragment.return = workInProgress;
|
||||
@@ -2230,7 +2231,7 @@ function updateSuspenseFallbackChildren(
|
||||
if (
|
||||
// In legacy mode, we commit the primary tree as if it successfully
|
||||
// completed, even though it's in an inconsistent state.
|
||||
(mode & BlockingMode) === NoMode &&
|
||||
(mode & ConcurrentMode) === NoMode &&
|
||||
// Make sure we're on the second pass, i.e. the primary child fragment was
|
||||
// already cloned. In legacy mode, the only case where this isn't true is
|
||||
// when DevTools forces us to display a fallback; we skip the first render
|
||||
@@ -2352,7 +2353,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
primaryChildFragment.sibling = fallbackChildFragment;
|
||||
workInProgress.child = primaryChildFragment;
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) !== NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
|
||||
// We will have dropped the effect list which contains the
|
||||
// deletion. We need to reconcile to delete the current child.
|
||||
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
|
||||
@@ -2368,12 +2369,12 @@ function mountDehydratedSuspenseComponent(
|
||||
): null | Fiber {
|
||||
// During the first pass, we'll bail out and not drill into the children.
|
||||
// Instead, we'll leave the content in place and try to hydrate it later.
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Cannot hydrate Suspense in legacy mode. Switch from ' +
|
||||
'ReactDOM.hydrate(element, container) to ' +
|
||||
'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
|
||||
'ReactDOM.createRoot(container, { hydrate: true })' +
|
||||
'.render(element) or remove the Suspense components from ' +
|
||||
'the server rendered components.',
|
||||
);
|
||||
@@ -2426,7 +2427,7 @@ function updateDehydratedSuspenseComponent(
|
||||
);
|
||||
}
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
@@ -2831,7 +2832,7 @@ function updateSuspenseListComponent(
|
||||
}
|
||||
pushSuspenseContext(workInProgress, suspenseContext);
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
// In legacy mode, SuspenseList doesn't work so we just
|
||||
// use make it a noop by treating it as the default revealOrder.
|
||||
workInProgress.memoizedState = null;
|
||||
|
||||
@@ -126,7 +126,6 @@ import {
|
||||
NoMode,
|
||||
ProfileMode,
|
||||
StrictLegacyMode,
|
||||
BlockingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
shouldSetTextContent,
|
||||
@@ -604,7 +603,6 @@ function updateOffscreenComponent(
|
||||
// Rendering a hidden tree.
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
// In legacy sync mode, don't defer the subtree. Render it now.
|
||||
// TODO: Figure out what we should do in Blocking mode.
|
||||
const nextState: OffscreenState = {
|
||||
baseLanes: NoLanes,
|
||||
cachePool: null,
|
||||
@@ -2117,7 +2115,10 @@ function mountSuspenseFallbackChildren(
|
||||
|
||||
let primaryChildFragment;
|
||||
let fallbackChildFragment;
|
||||
if ((mode & BlockingMode) === NoMode && progressedPrimaryFragment !== null) {
|
||||
if (
|
||||
(mode & ConcurrentMode) === NoMode &&
|
||||
progressedPrimaryFragment !== null
|
||||
) {
|
||||
// In legacy mode, we commit the primary tree as if it successfully
|
||||
// completed, even though it's in an inconsistent state.
|
||||
primaryChildFragment = progressedPrimaryFragment;
|
||||
@@ -2189,7 +2190,7 @@ function updateSuspensePrimaryChildren(
|
||||
children: primaryChildren,
|
||||
},
|
||||
);
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
primaryChildFragment.lanes = renderLanes;
|
||||
}
|
||||
primaryChildFragment.return = workInProgress;
|
||||
@@ -2230,7 +2231,7 @@ function updateSuspenseFallbackChildren(
|
||||
if (
|
||||
// In legacy mode, we commit the primary tree as if it successfully
|
||||
// completed, even though it's in an inconsistent state.
|
||||
(mode & BlockingMode) === NoMode &&
|
||||
(mode & ConcurrentMode) === NoMode &&
|
||||
// Make sure we're on the second pass, i.e. the primary child fragment was
|
||||
// already cloned. In legacy mode, the only case where this isn't true is
|
||||
// when DevTools forces us to display a fallback; we skip the first render
|
||||
@@ -2352,7 +2353,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
primaryChildFragment.sibling = fallbackChildFragment;
|
||||
workInProgress.child = primaryChildFragment;
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) !== NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
|
||||
// We will have dropped the effect list which contains the
|
||||
// deletion. We need to reconcile to delete the current child.
|
||||
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
|
||||
@@ -2368,12 +2369,12 @@ function mountDehydratedSuspenseComponent(
|
||||
): null | Fiber {
|
||||
// During the first pass, we'll bail out and not drill into the children.
|
||||
// Instead, we'll leave the content in place and try to hydrate it later.
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Cannot hydrate Suspense in legacy mode. Switch from ' +
|
||||
'ReactDOM.hydrate(element, container) to ' +
|
||||
'ReactDOM.createBlockingRoot(container, { hydrate: true })' +
|
||||
'ReactDOM.createRoot(container, { hydrate: true })' +
|
||||
'.render(element) or remove the Suspense components from ' +
|
||||
'the server rendered components.',
|
||||
);
|
||||
@@ -2426,7 +2427,7 @@ function updateDehydratedSuspenseComponent(
|
||||
);
|
||||
}
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
@@ -2831,7 +2832,7 @@ function updateSuspenseListComponent(
|
||||
}
|
||||
pushSuspenseContext(workInProgress, suspenseContext);
|
||||
|
||||
if ((workInProgress.mode & BlockingMode) === NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||
// In legacy mode, SuspenseList doesn't work so we just
|
||||
// use make it a noop by treating it as the default revealOrder.
|
||||
workInProgress.memoizedState = null;
|
||||
|
||||
@@ -55,12 +55,7 @@ import {
|
||||
LegacyHiddenComponent,
|
||||
CacheComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
ProfileMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
Ref,
|
||||
Update,
|
||||
@@ -1059,12 +1054,10 @@ function completeWork(
|
||||
}
|
||||
|
||||
if (nextDidTimeout && !prevDidTimeout) {
|
||||
// If this subtree is running in blocking mode we can suspend,
|
||||
// otherwise we won't suspend.
|
||||
// TODO: This will still suspend a synchronous tree if anything
|
||||
// in the concurrent tree already suspended during this render.
|
||||
// This is a known bug.
|
||||
if ((workInProgress.mode & BlockingMode) !== NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
|
||||
// TODO: Move this back to throwException because this is too late
|
||||
// if this is a large tree which is common for initial loads. We
|
||||
// don't know if we should restart a render or not until we get
|
||||
|
||||
@@ -55,12 +55,7 @@ import {
|
||||
LegacyHiddenComponent,
|
||||
CacheComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
ProfileMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
Ref,
|
||||
Update,
|
||||
@@ -1059,12 +1054,10 @@ function completeWork(
|
||||
}
|
||||
|
||||
if (nextDidTimeout && !prevDidTimeout) {
|
||||
// If this subtree is running in blocking mode we can suspend,
|
||||
// otherwise we won't suspend.
|
||||
// TODO: This will still suspend a synchronous tree if anything
|
||||
// in the concurrent tree already suspended during this render.
|
||||
// This is a known bug.
|
||||
if ((workInProgress.mode & BlockingMode) !== NoMode) {
|
||||
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
|
||||
// TODO: Move this back to throwException because this is too late
|
||||
// if this is a large tree which is common for initial loads. We
|
||||
// don't know if we should restart a render or not until we get
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
StrictEffectsMode,
|
||||
} from './ReactTypeOfMode';
|
||||
@@ -1829,7 +1829,7 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
|
||||
|
||||
const setId = mountState(id)[1];
|
||||
|
||||
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
|
||||
if ((currentlyRenderingFiber.mode & ConcurrentMode) === NoMode) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableStrictEffects &&
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
StrictEffectsMode,
|
||||
} from './ReactTypeOfMode';
|
||||
@@ -1829,7 +1829,7 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
|
||||
|
||||
const setId = mountState(id)[1];
|
||||
|
||||
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
|
||||
if ((currentlyRenderingFiber.mode & ConcurrentMode) === NoMode) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableStrictEffects &&
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {unstable_getThreadID} from 'scheduler/tracing';
|
||||
import {initializeUpdateQueue} from './ReactUpdateQueue.new';
|
||||
import {LegacyRoot, BlockingRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
|
||||
function FiberRootNode(containerInfo, tag, hydrate) {
|
||||
this.tag = tag;
|
||||
@@ -73,9 +73,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
|
||||
|
||||
if (__DEV__) {
|
||||
switch (tag) {
|
||||
case BlockingRoot:
|
||||
this._debugRootType = 'createBlockingRoot()';
|
||||
break;
|
||||
case ConcurrentRoot:
|
||||
this._debugRootType = 'createRoot()';
|
||||
break;
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {unstable_getThreadID} from 'scheduler/tracing';
|
||||
import {initializeUpdateQueue} from './ReactUpdateQueue.old';
|
||||
import {LegacyRoot, BlockingRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
|
||||
function FiberRootNode(containerInfo, tag, hydrate) {
|
||||
this.tag = tag;
|
||||
@@ -73,9 +73,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
|
||||
|
||||
if (__DEV__) {
|
||||
switch (tag) {
|
||||
case BlockingRoot:
|
||||
this._debugRootType = 'createBlockingRoot()';
|
||||
break;
|
||||
case ConcurrentRoot:
|
||||
this._debugRootType = 'createRoot()';
|
||||
break;
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
ForceUpdateForLegacySuspense,
|
||||
} from './ReactFiberFlags';
|
||||
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
|
||||
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
@@ -214,7 +214,7 @@ function throwException(
|
||||
// A legacy mode Suspense quirk, only relevant to hook components.
|
||||
const tag = sourceFiber.tag;
|
||||
if (
|
||||
(sourceFiber.mode & BlockingMode) === NoMode &&
|
||||
(sourceFiber.mode & ConcurrentMode) === NoMode &&
|
||||
(tag === FunctionComponent ||
|
||||
tag === ForwardRef ||
|
||||
tag === SimpleMemoComponent)
|
||||
@@ -255,13 +255,13 @@ function throwException(
|
||||
wakeables.add(wakeable);
|
||||
}
|
||||
|
||||
// If the boundary is outside of blocking mode, we should *not*
|
||||
// If the boundary is in legacy mode, we should *not*
|
||||
// suspend the commit. Pretend as if the suspended component rendered
|
||||
// null and keep rendering. In the commit phase, we'll schedule a
|
||||
// subsequent synchronous update to re-render the Suspense.
|
||||
//
|
||||
// Note: It doesn't matter whether the component that suspended was
|
||||
// inside a blocking mode tree. If the Suspense is outside of it, we
|
||||
// inside a concurrent mode tree. If the Suspense is outside of it, we
|
||||
// should *not* suspend the commit.
|
||||
//
|
||||
// If the suspense boundary suspended itself suspended, we don't have to
|
||||
@@ -269,7 +269,7 @@ function throwException(
|
||||
// directly do a second pass over the fallback in this render and
|
||||
// pretend we meant to render that directly.
|
||||
if (
|
||||
(workInProgress.mode & BlockingMode) === NoMode &&
|
||||
(workInProgress.mode & ConcurrentMode) === NoMode &&
|
||||
workInProgress !== returnFiber
|
||||
) {
|
||||
workInProgress.flags |= DidCapture;
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
ForceUpdateForLegacySuspense,
|
||||
} from './ReactFiberFlags';
|
||||
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.old';
|
||||
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
@@ -214,7 +214,7 @@ function throwException(
|
||||
// A legacy mode Suspense quirk, only relevant to hook components.
|
||||
const tag = sourceFiber.tag;
|
||||
if (
|
||||
(sourceFiber.mode & BlockingMode) === NoMode &&
|
||||
(sourceFiber.mode & ConcurrentMode) === NoMode &&
|
||||
(tag === FunctionComponent ||
|
||||
tag === ForwardRef ||
|
||||
tag === SimpleMemoComponent)
|
||||
@@ -255,13 +255,13 @@ function throwException(
|
||||
wakeables.add(wakeable);
|
||||
}
|
||||
|
||||
// If the boundary is outside of blocking mode, we should *not*
|
||||
// If the boundary is in legacy mode, we should *not*
|
||||
// suspend the commit. Pretend as if the suspended component rendered
|
||||
// null and keep rendering. In the commit phase, we'll schedule a
|
||||
// subsequent synchronous update to re-render the Suspense.
|
||||
//
|
||||
// Note: It doesn't matter whether the component that suspended was
|
||||
// inside a blocking mode tree. If the Suspense is outside of it, we
|
||||
// inside a concurrent mode tree. If the Suspense is outside of it, we
|
||||
// should *not* suspend the commit.
|
||||
//
|
||||
// If the suspense boundary suspended itself suspended, we don't have to
|
||||
@@ -269,7 +269,7 @@ function throwException(
|
||||
// directly do a second pass over the fallback in this render and
|
||||
// pretend we meant to render that directly.
|
||||
if (
|
||||
(workInProgress.mode & BlockingMode) === NoMode &&
|
||||
(workInProgress.mode & ConcurrentMode) === NoMode &&
|
||||
workInProgress !== returnFiber
|
||||
) {
|
||||
workInProgress.flags |= DidCapture;
|
||||
|
||||
@@ -106,7 +106,6 @@ import {
|
||||
NoMode,
|
||||
StrictLegacyMode,
|
||||
ProfileMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
@@ -392,7 +391,7 @@ export function getCurrentTime() {
|
||||
export function requestUpdateLane(fiber: Fiber): Lane {
|
||||
// Special cases
|
||||
const mode = fiber.mode;
|
||||
if ((mode & BlockingMode) === NoMode) {
|
||||
if ((mode & ConcurrentMode) === NoMode) {
|
||||
return (SyncLane: Lane);
|
||||
} else if ((mode & ConcurrentMode) === NoMode) {
|
||||
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
|
||||
@@ -483,7 +482,7 @@ function requestRetryLane(fiber: Fiber) {
|
||||
|
||||
// Special cases
|
||||
const mode = fiber.mode;
|
||||
if ((mode & BlockingMode) === NoMode) {
|
||||
if ((mode & ConcurrentMode) === NoMode) {
|
||||
return (SyncLane: Lane);
|
||||
} else if ((mode & ConcurrentMode) === NoMode) {
|
||||
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
|
||||
@@ -677,7 +676,7 @@ export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
|
||||
// Requires some refactoring. Not a big deal though since it's rare for
|
||||
// concurrent apps to have more than a single root.
|
||||
workInProgressRoot !== null &&
|
||||
(fiber.mode & BlockingMode) !== NoMode &&
|
||||
(fiber.mode & ConcurrentMode) !== NoMode &&
|
||||
// If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
|
||||
// then don't treat this as an interleaved update. This pattern is
|
||||
// accompanied by a warning but we haven't fully deprecated it yet. We can
|
||||
@@ -2625,7 +2624,7 @@ function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(fiber.mode & (BlockingMode | ConcurrentMode))) {
|
||||
if (!(fiber.mode & ConcurrentMode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3004,7 +3003,7 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
|
||||
didWarnAboutUnmockedScheduler === false &&
|
||||
Scheduler.unstable_flushAllWithoutAsserting === undefined
|
||||
) {
|
||||
if (fiber.mode & BlockingMode || fiber.mode & ConcurrentMode) {
|
||||
if (fiber.mode & ConcurrentMode) {
|
||||
didWarnAboutUnmockedScheduler = true;
|
||||
console.error(
|
||||
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
|
||||
|
||||
@@ -106,7 +106,6 @@ import {
|
||||
NoMode,
|
||||
StrictLegacyMode,
|
||||
ProfileMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
@@ -392,7 +391,7 @@ export function getCurrentTime() {
|
||||
export function requestUpdateLane(fiber: Fiber): Lane {
|
||||
// Special cases
|
||||
const mode = fiber.mode;
|
||||
if ((mode & BlockingMode) === NoMode) {
|
||||
if ((mode & ConcurrentMode) === NoMode) {
|
||||
return (SyncLane: Lane);
|
||||
} else if ((mode & ConcurrentMode) === NoMode) {
|
||||
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
|
||||
@@ -483,7 +482,7 @@ function requestRetryLane(fiber: Fiber) {
|
||||
|
||||
// Special cases
|
||||
const mode = fiber.mode;
|
||||
if ((mode & BlockingMode) === NoMode) {
|
||||
if ((mode & ConcurrentMode) === NoMode) {
|
||||
return (SyncLane: Lane);
|
||||
} else if ((mode & ConcurrentMode) === NoMode) {
|
||||
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
|
||||
@@ -677,7 +676,7 @@ export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
|
||||
// Requires some refactoring. Not a big deal though since it's rare for
|
||||
// concurrent apps to have more than a single root.
|
||||
workInProgressRoot !== null &&
|
||||
(fiber.mode & BlockingMode) !== NoMode &&
|
||||
(fiber.mode & ConcurrentMode) !== NoMode &&
|
||||
// If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
|
||||
// then don't treat this as an interleaved update. This pattern is
|
||||
// accompanied by a warning but we haven't fully deprecated it yet. We can
|
||||
@@ -2625,7 +2624,7 @@ function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(fiber.mode & (BlockingMode | ConcurrentMode))) {
|
||||
if (!(fiber.mode & ConcurrentMode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3004,7 +3003,7 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
|
||||
didWarnAboutUnmockedScheduler === false &&
|
||||
Scheduler.unstable_flushAllWithoutAsserting === undefined
|
||||
) {
|
||||
if (fiber.mode & BlockingMode || fiber.mode & ConcurrentMode) {
|
||||
if (fiber.mode & ConcurrentMode) {
|
||||
didWarnAboutUnmockedScheduler = true;
|
||||
console.error(
|
||||
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
|
||||
|
||||
+2
-3
@@ -7,8 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type RootTag = 0 | 1 | 2;
|
||||
export type RootTag = 0 | 1;
|
||||
|
||||
export const LegacyRoot = 0;
|
||||
export const BlockingRoot = 1;
|
||||
export const ConcurrentRoot = 2;
|
||||
export const ConcurrentRoot = 1;
|
||||
|
||||
+6
-7
@@ -10,10 +10,9 @@
|
||||
export type TypeOfMode = number;
|
||||
|
||||
export const NoMode = /* */ 0b000000;
|
||||
// TODO: Remove BlockingMode and ConcurrentMode by reading from the root tag instead
|
||||
export const BlockingMode = /* */ 0b000001;
|
||||
export const ConcurrentMode = /* */ 0b000010;
|
||||
export const ProfileMode = /* */ 0b000100;
|
||||
export const DebugTracingMode = /* */ 0b001000;
|
||||
export const StrictLegacyMode = /* */ 0b010000;
|
||||
export const StrictEffectsMode = /* */ 0b100000;
|
||||
// TODO: Remove ConcurrentMode by reading from the root tag instead
|
||||
export const ConcurrentMode = /* */ 0b000001;
|
||||
export const ProfileMode = /* */ 0b000010;
|
||||
export const DebugTracingMode = /* */ 0b000100;
|
||||
export const StrictLegacyMode = /* */ 0b001000;
|
||||
export const StrictEffectsMode = /* */ 0b010000;
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let ReactCache;
|
||||
let Suspense;
|
||||
let TextResource;
|
||||
|
||||
describe('ReactBlockingMode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
ReactCache = require('react-cache');
|
||||
Suspense = React.Suspense;
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(
|
||||
([text, ms = 0]) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
setTimeout(() => {
|
||||
Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
|
||||
resolve(text);
|
||||
}, ms),
|
||||
);
|
||||
},
|
||||
([text, ms]) => text,
|
||||
);
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
Scheduler.unstable_yieldValue(props.text);
|
||||
return props.text;
|
||||
}
|
||||
|
||||
function AsyncText(props) {
|
||||
const text = props.text;
|
||||
try {
|
||||
TextResource.read([props.text, props.ms]);
|
||||
Scheduler.unstable_yieldValue(text);
|
||||
return props.text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
it('updates flush without yielding in the next event', () => {
|
||||
const root = ReactNoop.createBlockingRoot();
|
||||
|
||||
root.render(
|
||||
<>
|
||||
<Text text="A" />
|
||||
<Text text="B" />
|
||||
<Text text="C" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Nothing should have rendered yet
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Everything should render immediately in the next event
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
|
||||
expect(root).toMatchRenderedOutput('ABC');
|
||||
});
|
||||
|
||||
it('layout updates flush synchronously in same event', () => {
|
||||
const {useLayoutEffect} = React;
|
||||
|
||||
function App() {
|
||||
useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue('Layout effect');
|
||||
});
|
||||
return <Text text="Hi" />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createBlockingRoot();
|
||||
root.render(<App />);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
expect(Scheduler).toFlushExpired(['Hi', 'Layout effect']);
|
||||
expect(root).toMatchRenderedOutput('Hi');
|
||||
});
|
||||
|
||||
it('uses proper Suspense semantics, not legacy ones', async () => {
|
||||
const root = ReactNoop.createBlockingRoot();
|
||||
root.render(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<span>
|
||||
<Text text="A" />
|
||||
</span>
|
||||
<span>
|
||||
<AsyncText text="B" />
|
||||
</span>
|
||||
<span>
|
||||
<Text text="C" />
|
||||
</span>
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushExpired(['A', 'Suspend! [B]', 'C', 'Loading...']);
|
||||
// In Legacy Mode, A and B would mount in a hidden primary tree. In Batched
|
||||
// and Concurrent Mode, nothing in the primary tree should mount. But the
|
||||
// fallback should mount immediately.
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>A</span>
|
||||
<span>B</span>
|
||||
<span>C</span>
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
it('flushSync does not flush batched work', () => {
|
||||
const {useState, forwardRef, useImperativeHandle} = React;
|
||||
const root = ReactNoop.createBlockingRoot();
|
||||
|
||||
const Foo = forwardRef(({label}, ref) => {
|
||||
const [step, setStep] = useState(0);
|
||||
useImperativeHandle(ref, () => ({setStep}));
|
||||
return <Text text={label + step} />;
|
||||
});
|
||||
|
||||
const foo1 = React.createRef(null);
|
||||
const foo2 = React.createRef(null);
|
||||
root.render(
|
||||
<>
|
||||
<Foo label="A" ref={foo1} />
|
||||
<Foo label="B" ref={foo2} />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Mount
|
||||
expect(Scheduler).toFlushExpired(['A0', 'B0']);
|
||||
expect(root).toMatchRenderedOutput('A0B0');
|
||||
|
||||
// Schedule a batched update to the first sibling
|
||||
ReactNoop.batchedUpdates(() => foo1.current.setStep(1));
|
||||
|
||||
// Before it flushes, update the second sibling inside flushSync
|
||||
ReactNoop.batchedUpdates(() =>
|
||||
ReactNoop.flushSync(() => {
|
||||
foo2.current.setStep(1);
|
||||
}),
|
||||
);
|
||||
|
||||
// Only the second update should have flushed synchronously
|
||||
expect(Scheduler).toHaveYielded(['B1']);
|
||||
expect(root).toMatchRenderedOutput('A0B1');
|
||||
|
||||
// Now flush the first update
|
||||
expect(Scheduler).toFlushExpired(['A1']);
|
||||
expect(root).toMatchRenderedOutput('A1B1');
|
||||
});
|
||||
});
|
||||
@@ -1870,10 +1870,6 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render('Error when completing root');
|
||||
expect(Scheduler).toFlushAndThrow('Error when completing root');
|
||||
|
||||
const blockingRoot = ReactNoop.createBlockingRoot();
|
||||
blockingRoot.render('Error when completing root');
|
||||
expect(Scheduler).toFlushAndThrow('Error when completing root');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -123,46 +123,4 @@ describe('ReactOffscreen', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('does not defer in blocking mode', async () => {
|
||||
let setState;
|
||||
function Foo() {
|
||||
const [state, _setState] = useState('A');
|
||||
setState = _setState;
|
||||
return <Text text={state} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createBlockingRoot();
|
||||
await ReactNoop.act(async () => {
|
||||
root.render(
|
||||
<>
|
||||
<LegacyHidden mode="hidden">
|
||||
<Foo />
|
||||
</LegacyHidden>
|
||||
<Text text="Outside" />
|
||||
</>,
|
||||
);
|
||||
// Should not defer the hidden tree
|
||||
expect(Scheduler).toFlushUntilNextPaint(['A', 'Outside']);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="A" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Test that the children can be updated
|
||||
await ReactNoop.act(async () => {
|
||||
setState('B');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['B']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="B" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,13 +170,6 @@ describe('ReactSuspenseFuzz', () => {
|
||||
expect(legacyOutput).toEqual(expectedOutput);
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
|
||||
resetCache();
|
||||
const batchedBlockingRoot = ReactNoop.createBlockingRoot();
|
||||
batchedBlockingRoot.render(children);
|
||||
resolveAllTasks();
|
||||
const batchedSyncOutput = batchedBlockingRoot.getChildrenAsJSX();
|
||||
expect(batchedSyncOutput).toEqual(expectedOutput);
|
||||
|
||||
resetCache();
|
||||
const concurrentRoot = ReactNoop.createRoot();
|
||||
concurrentRoot.render(children);
|
||||
|
||||
@@ -66,23 +66,6 @@ describe('ReactStrictMode', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should support overriding default via createBlockingRoot option', () => {
|
||||
act(() => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOM.createBlockingRoot(container, {
|
||||
unstable_strictModeLevel: 0,
|
||||
});
|
||||
root.render(<Component label="A" />);
|
||||
});
|
||||
|
||||
expect(log).toEqual([
|
||||
'A: render',
|
||||
'A: useLayoutEffect mount',
|
||||
'A: useEffect mount',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should disable strict mode if level 0 is specified', () => {
|
||||
act(() => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
|
||||
// this feature flag only impacts StrictEffectsMode.
|
||||
export const enableStrictEffects = false;
|
||||
|
||||
// If TRUE, trees rendered with createRoot (and createBlockingRoot) APIs will be StrictEffectsMode.
|
||||
// If TRUE, trees rendered with createRoot will be StrictEffectsMode.
|
||||
// If FALSE, these trees will be StrictLegacyMode.
|
||||
export const createRootStrictEffectsByDefault = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user