From b820aed30cf4e8defab76ba191eb017480f8c502 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Fri, 9 Apr 2021 20:35:13 -0400 Subject: [PATCH] Add unstable_concurrentUpdatesByDefault --- .../src/__tests__/ReactDOMRoot-test.js | 31 +++++++++++++++++++ packages/react-dom/src/client/ReactDOMRoot.js | 6 ++++ .../react-reconciler/src/ReactFiber.new.js | 5 +++ .../react-reconciler/src/ReactFiber.old.js | 5 +++ .../src/ReactFiberReconciler.new.js | 2 ++ .../src/ReactFiberReconciler.old.js | 2 ++ .../src/ReactFiberRoot.new.js | 7 ++++- .../src/ReactFiberRoot.old.js | 7 ++++- .../src/ReactFiberWorkLoop.new.js | 10 +++++- .../src/ReactFiberWorkLoop.old.js | 10 +++++- .../react-reconciler/src/ReactTypeOfMode.js | 13 ++++---- .../src/ReactTestRenderer.js | 6 ++++ 12 files changed, 94 insertions(+), 10 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js index 25a560c5c32af..c5e6e4898ebe9 100644 --- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js @@ -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; @@ -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__) { @@ -316,4 +318,33 @@ 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
{value}
; + } + + await act(async () => { + root.render(); + }); + + expect(container.textContent).toEqual('a'); + + await act(async () => { + root.render(); + + expect(Scheduler).toHaveYielded(['a']); + expect(container.textContent).toEqual('a'); + + expect(Scheduler).toFlushAndYieldThrough(['b']); + expect(container.textContent).toEqual('a'); + }); + expect(container.textContent).toEqual('b'); + }); }); diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 56532d5d67488..068a080a1cef1 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -28,6 +28,7 @@ export type RootOptions = { ... }, unstable_strictModeLevel?: number, + unstable_concurrentUpdatesByDefault?: boolean, ... }; @@ -125,6 +126,10 @@ function createRootImpl( options != null && options.unstable_strictModeLevel != null ? options.unstable_strictModeLevel : null; + const concurrentUpdatesByDefaultOverride = + options != null && options.unstable_concurrentUpdatesByDefault != null + ? options.unstable_concurrentUpdatesByDefault + : null; const root = createContainer( container, @@ -132,6 +137,7 @@ function createRootImpl( hydrate, hydrationCallbacks, strictModeLevelOverride, + concurrentUpdatesByDefaultOverride, ); markContainerAsRoot(root.current, container); diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index 95a5340b18730..2401cd04cf3fd 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -68,6 +68,7 @@ import { ProfileMode, StrictLegacyMode, StrictEffectsMode, + ConcurrentUpdatesByDefaultMode, } from './ReactTypeOfMode'; import { REACT_FORWARD_REF_TYPE, @@ -420,6 +421,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) { @@ -440,6 +442,9 @@ export function createHostRootFiber( mode |= StrictLegacyMode; } } + if (concurrentUpdatesByDefaultOverride) { + mode |= ConcurrentUpdatesByDefaultMode; + } } else { mode = NoMode; } diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 159557e86a166..7228b33710b66 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -68,6 +68,7 @@ import { ProfileMode, StrictLegacyMode, StrictEffectsMode, + ConcurrentUpdatesByDefaultMode, } from './ReactTypeOfMode'; import { REACT_FORWARD_REF_TYPE, @@ -420,6 +421,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) { @@ -440,6 +442,9 @@ export function createHostRootFiber( mode |= StrictLegacyMode; } } + if (concurrentUpdatesByDefaultOverride) { + mode |= ConcurrentUpdatesByDefaultMode; + } } else { mode = NoMode; } diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index 4572e4d9de4ba..b782062100393 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -249,6 +249,7 @@ export function createContainer( hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, strictModeLevelOverride: null | number, + concurrentUpdatesByDefaultOverride: null | boolean, ): OpaqueRoot { return createFiberRoot( containerInfo, @@ -256,6 +257,7 @@ export function createContainer( hydrate, hydrationCallbacks, strictModeLevelOverride, + concurrentUpdatesByDefaultOverride, ); } diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index abd5b9094c59c..c7df1b8c57462 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -249,6 +249,7 @@ export function createContainer( hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, strictModeLevelOverride: null | number, + concurrentUpdatesByDefaultOverride: null | boolean, ): OpaqueRoot { return createFiberRoot( containerInfo, @@ -256,6 +257,7 @@ export function createContainer( hydrate, hydrationCallbacks, strictModeLevelOverride, + concurrentUpdatesByDefaultOverride, ); } diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 9201a35980753..4f92bc2f05db6 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -105,6 +105,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) { @@ -113,7 +114,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; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index f2968b314ecae..a69ca37e0d4a4 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -105,6 +105,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) { @@ -113,7 +114,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; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index df1b2653e204a..dfe7993bc3d93 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -107,6 +107,7 @@ import { StrictLegacyMode, ProfileMode, ConcurrentMode, + ConcurrentUpdatesByDefaultMode, } from './ReactTypeOfMode'; import { HostRoot, @@ -440,6 +441,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { if (updateLane !== NoLane) { if ( enableSyncDefaultUpdates && + (mode & ConcurrentUpdatesByDefaultMode) === NoMode && (updateLane === InputContinuousLane || updateLane === InputContinuousHydrationLane) ) { @@ -457,6 +459,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { const eventLane: Lane = (getCurrentEventPriority(): any); if ( enableSyncDefaultUpdates && + (mode & ConcurrentUpdatesByDefaultMode) === NoMode && (eventLane === InputContinuousLane || eventLane === InputContinuousHydrationLane) ) { @@ -716,6 +719,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { let newCallbackNode; if ( enableSyncDefaultUpdates && + (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode && (newCallbackPriority === DefaultLane || newCallbackPriority === DefaultHydrationLane) ) { @@ -1058,7 +1062,11 @@ function performSyncWorkOnRoot(root) { const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; - if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) { + if ( + enableSyncDefaultUpdates && + (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode && + !includesSomeLane(lanes, SyncLane) + ) { finishConcurrentRender(root, exitStatus, lanes); } else { commitRoot(root); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 07f8a38b8bc0a..185f4de1f8065 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -107,6 +107,7 @@ import { StrictLegacyMode, ProfileMode, ConcurrentMode, + ConcurrentUpdatesByDefaultMode, } from './ReactTypeOfMode'; import { HostRoot, @@ -440,6 +441,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { if (updateLane !== NoLane) { if ( enableSyncDefaultUpdates && + (mode & ConcurrentUpdatesByDefaultMode) === NoMode && (updateLane === InputContinuousLane || updateLane === InputContinuousHydrationLane) ) { @@ -457,6 +459,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { const eventLane: Lane = (getCurrentEventPriority(): any); if ( enableSyncDefaultUpdates && + (mode & ConcurrentUpdatesByDefaultMode) === NoMode && (eventLane === InputContinuousLane || eventLane === InputContinuousHydrationLane) ) { @@ -716,6 +719,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { let newCallbackNode; if ( enableSyncDefaultUpdates && + (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode && (newCallbackPriority === DefaultLane || newCallbackPriority === DefaultHydrationLane) ) { @@ -1058,7 +1062,11 @@ function performSyncWorkOnRoot(root) { const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; - if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) { + if ( + enableSyncDefaultUpdates && + (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode && + !includesSomeLane(lanes, SyncLane) + ) { finishConcurrentRender(root, exitStatus, lanes); } else { commitRoot(root); diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js index 466363fabd4e8..f16d2c51b76c0 100644 --- a/packages/react-reconciler/src/ReactTypeOfMode.js +++ b/packages/react-reconciler/src/ReactTypeOfMode.js @@ -9,10 +9,11 @@ export type TypeOfMode = number; -export const NoMode = /* */ 0b000000; +export const NoMode = /* */ 0b000000; // 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; +export const ConcurrentMode = /* */ 0b000001; +export const ProfileMode = /* */ 0b000010; +export const DebugTracingMode = /* */ 0b000100; +export const StrictLegacyMode = /* */ 0b001000; +export const StrictEffectsMode = /* */ 0b010000; +export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000; diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 5c74c6e9e310c..6497f5efd77c3 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -58,6 +58,7 @@ type TestRendererOptions = { createNodeMock: (element: React$Element) => any, unstable_isConcurrent: boolean, unstable_strictModeLevel: number, + unstable_concurrentUpdatesByDefault: boolean, ... }; @@ -436,6 +437,7 @@ function create(element: React$Element, options: TestRendererOptions) { let createNodeMock = defaultTestOptions.createNodeMock; let isConcurrent = false; let strictModeLevel = null; + let concurrentUpdatesByDefault = null; if (typeof options === 'object' && options !== null) { if (typeof options.createNodeMock === 'function') { createNodeMock = options.createNodeMock; @@ -446,6 +448,9 @@ function create(element: React$Element, options: TestRendererOptions) { if (options.unstable_strictModeLevel !== undefined) { strictModeLevel = options.unstable_strictModeLevel; } + if (options.unstable_concurrentUpdatesByDefault !== undefined) { + concurrentUpdatesByDefault = options.unstable_concurrentUpdatesByDefault; + } } let container = { children: [], @@ -458,6 +463,7 @@ function create(element: React$Element, options: TestRendererOptions) { false, null, strictModeLevel, + concurrentUpdatesByDefault, ); invariant(root != null, 'something went wrong'); updateContainer(element, root, null, null);