Skip to content
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
35 changes: 35 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMRoot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let React = require('react');
let ReactDOM = require('react-dom');
let ReactDOMServer = require('react-dom/server');
let Scheduler = require('scheduler');
let act;

describe('ReactDOMRoot', () => {
let container;
Expand All @@ -24,6 +25,7 @@ describe('ReactDOMRoot', () => {
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
act = require('react-dom/test-utils').unstable_concurrentAct;
});

if (!__EXPERIMENTAL__) {
Expand Down Expand Up @@ -316,4 +318,37 @@ describe('ReactDOMRoot', () => {
{withoutStack: true},
);
});

// @gate experimental
it('opts-in to concurrent default updates', async () => {
const root = ReactDOM.unstable_createRoot(container, {
unstable_concurrentUpdatesByDefault: true,
});

function Foo({value}) {
Scheduler.unstable_yieldValue(value);
return <div>{value}</div>;
}

await act(async () => {
root.render(<Foo value="a" />);
});

expect(container.textContent).toEqual('a');

await act(async () => {
root.render(<Foo value="b" />);

expect(Scheduler).toHaveYielded(['a']);
expect(container.textContent).toEqual('a');

expect(Scheduler).toFlushAndYieldThrough(['b']);
if (gate(flags => flags.allowConcurrentByDefault)) {
expect(container.textContent).toEqual('a');
} else {
expect(container.textContent).toEqual('b');
}
});
expect(container.textContent).toEqual('b');
});
});
11 changes: 11 additions & 0 deletions packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type RootOptions = {
...
},
unstable_strictModeLevel?: number,
unstable_concurrentUpdatesByDefault?: boolean,
...
};

Expand All @@ -52,6 +53,7 @@ import {
} from 'react-reconciler/src/ReactFiberReconciler';
import invariant from 'shared/invariant';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';

function ReactDOMRoot(container: Container, options: void | RootOptions) {
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
Expand Down Expand Up @@ -126,12 +128,21 @@ function createRootImpl(
? options.unstable_strictModeLevel
: null;

let concurrentUpdatesByDefaultOverride = null;
if (allowConcurrentByDefault) {
concurrentUpdatesByDefaultOverride =
options != null && options.unstable_concurrentUpdatesByDefault != null
? options.unstable_concurrentUpdatesByDefault
: null;
}

const root = createContainer(
container,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
markContainerAsRoot(root.current, container);

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null, null);
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null, null);
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
Expand Down
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
enableStrictEffects,
enableProfilerTimer,
enableScopeAPI,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
Expand Down Expand Up @@ -68,6 +70,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
Expand Down Expand Up @@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
Expand All @@ -440,6 +444,15 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
if (
// We only use this flag for our repo tests to check both behaviors.
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
Expand Down
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
enableStrictEffects,
enableProfilerTimer,
enableScopeAPI,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
Expand Down Expand Up @@ -68,6 +70,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
Expand Down Expand Up @@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
Expand All @@ -440,6 +444,15 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
if (
// We only use this flag for our repo tests to check both behaviors.
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
Expand Down
34 changes: 20 additions & 14 deletions packages/react-reconciler/src/ReactFiberLane.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';

export const SyncLanePriority: LanePriority = 12;

Expand Down Expand Up @@ -318,11 +319,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (
// TODO: Check for root override, once that lands
enableSyncDefaultUpdates &&
(nextLanes & InputContinuousLane) !== NoLanes
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// When updates are sync by default, we entangle continous priority updates
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
Expand Down Expand Up @@ -527,17 +529,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
// finish rendering without yielding execution.
return false;
}
if (enableSyncDefaultUpdates) {
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
// TODO: Check for root override, once that lands
return (lanes & SyncDefaultLanes) === NoLanes;
} else {

if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
return true;
}

const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) === NoLanes;
}

export function isTransitionLane(lane: Lane) {
Expand Down
34 changes: 20 additions & 14 deletions packages/react-reconciler/src/ReactFiberLane.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';

export const SyncLanePriority: LanePriority = 12;

Expand Down Expand Up @@ -318,11 +319,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}

if (
// TODO: Check for root override, once that lands
enableSyncDefaultUpdates &&
(nextLanes & InputContinuousLane) !== NoLanes
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// When updates are sync by default, we entangle continous priority updates
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
Expand Down Expand Up @@ -527,17 +529,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
// finish rendering without yielding execution.
return false;
}
if (enableSyncDefaultUpdates) {
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
// TODO: Check for root override, once that lands
return (lanes & SyncDefaultLanes) === NoLanes;
} else {

if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
return true;
}

const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) === NoLanes;
}

export function isTransitionLane(lane: Lane) {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
}

Expand Down
7 changes: 6 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -107,7 +108,11 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
const uninitializedFiber = createHostRootFiber(
tag,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
7 changes: 6 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -107,7 +108,11 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
const uninitializedFiber = createHostRootFiber(
tag,
strictModeLevelOverride,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
Loading