diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index e3e7ffbf6d1b8..1a9b978f873ec 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -190,6 +190,10 @@ export type Fiber = {| // This is used to quickly determine if a subtree has no pending changes. childExpirationTime: ExpirationTime, + // an identifier that allows the context propagation algorithm to determine + // if this fiber has been visited during a previous propagation + propagationSigil: any, + // This is a pooled version of a Fiber. Every fiber that gets updated will // eventually have a pair. There are cases when we can clean up pairs to save // memory if we need to. @@ -275,6 +279,8 @@ function FiberNode( this.expirationTime = NoWork; this.childExpirationTime = NoWork; + this.propagationSigil = null; + this.alternate = null; if (enableProfilerTimer) { @@ -425,6 +431,7 @@ export function createWorkInProgress( workInProgress.childExpirationTime = current.childExpirationTime; workInProgress.expirationTime = current.expirationTime; + workInProgress.propagationSigil = current.propagationSigil; workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; @@ -803,6 +810,7 @@ export function assignFiberPropertiesInDEV( target.lastEffect = source.lastEffect; target.expirationTime = source.expirationTime; target.childExpirationTime = source.childExpirationTime; + target.propagationSigil = source.propagationSigil; target.alternate = source.alternate; if (enableProfilerTimer) { target.actualDuration = source.actualDuration; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 600389dc32b40..8ae8c9ac048a4 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -13,6 +13,7 @@ import type {FiberRoot} from './ReactFiberRoot'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {SuspenseState} from './ReactFiberSuspenseComponent'; import type {SuspenseContext} from './ReactFiberSuspenseContext'; +import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; import checkPropTypes from 'prop-types/checkPropTypes'; @@ -56,6 +57,7 @@ import { enableProfilerTimer, enableSuspenseServerRenderer, enableEventAPI, + enableLazyContextPropagation, } from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import shallowEqual from 'shared/shallowEqual'; @@ -124,9 +126,13 @@ import { import { pushProvider, propagateContextChange, + propagateContexts, readContext, prepareToReadContext, calculateChangedBits, + currentPropagationSigil, + updateFromContextDependencies, + checkContextDependencies, } from './ReactFiberNewContext'; import {resetHooks, renderWithHooks, bailoutHooks} from './ReactFiberHooks'; import {stopProfilerTimerIfRunning} from './ReactProfilerTimer'; @@ -169,6 +175,8 @@ import {requestCurrentTime, retryTimedOutBoundary} from './ReactFiberWorkLoop'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; let didReceiveUpdate: boolean = false; +let uncheckedContextOnBailout: boolean = false; +let preventBailout: boolean = false; let didWarnAboutBadClass; let didWarnAboutModulePatternComponent; @@ -327,7 +335,13 @@ function updateForwardRef( ); } - if (current !== null && !didReceiveUpdate) { + if ( + current !== null && + !didReceiveUpdate && + // this check uses context dependencies from the renderWithHooks call which + // incorporates nextProps + canBailout(workInProgress, renderExpirationTime) + ) { bailoutHooks(current, workInProgress, renderExpirationTime); return bailoutOnAlreadyFinishedWork( current, @@ -434,7 +448,14 @@ function updateMemoComponent( // Default to shallow comparison let compare = Component.compare; compare = compare !== null ? compare : shallowEqual; - if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) { + if ( + compare(prevProps, nextProps) && + current.ref === workInProgress.ref && + // this bailout check uses context dependencies from the previous render + // since we have not prepareToReadContext yet and have not rendered the + // component from workInProgress yet + canBailout(workInProgress, renderExpirationTime) + ) { return bailoutOnAlreadyFinishedWork( current, workInProgress, @@ -496,6 +517,10 @@ function updateSimpleMemoComponent( if ( shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref && + // this bailout check uses context dependencies from the previous render + // since we have not prepareToReadContext yet and have not rendered the + // component from workInProgress yet + canBailout(workInProgress, renderExpirationTime) && // Prevent bailout if the implementation changed due to hot reload: (__DEV__ ? workInProgress.type === current.type : true) ) { @@ -647,7 +672,15 @@ function updateFunctionComponent( ); } - if (current !== null && !didReceiveUpdate) { + if ( + current !== null && + !didReceiveUpdate && + // this bailout is using context dependencies from the renderWithHooks call + // which incorporates nextProps. + // we may be able to skip if we ran the context check in simple memo, forwardRef + // or other updaters prior to updateFunctionComponent + canBailout(workInProgress, renderExpirationTime) + ) { bailoutHooks(current, workInProgress, renderExpirationTime); return bailoutOnAlreadyFinishedWork( current, @@ -701,6 +734,13 @@ function updateClassComponent( } else { hasContext = false; } + if (enableLazyContextPropagation) { + // class components cannot use context selectors (Yet) so we can check + // dependencies for just this fiber and if needed result in a ForceUpdate + // this most closely resembles the old context propagation behahvior + updateFromContextDependencies(workInProgress, renderExpirationTime); + uncheckedContextOnBailout = false; + } prepareToReadContext(workInProgress, renderExpirationTime); const instance = workInProgress.stateNode; @@ -783,7 +823,7 @@ function finishClassComponent( const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect; - if (!shouldUpdate && !didCaptureError) { + if (!shouldUpdate && !didCaptureError && !preventBailout) { // Context providers should defer to sCU for rendering if (hasContext) { invalidateContextProvider(workInProgress, Component, false); @@ -862,7 +902,6 @@ function finishClassComponent( if (hasContext) { invalidateContextProvider(workInProgress, Component, true); } - return workInProgress.child; } @@ -904,10 +943,12 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) { // Caution: React DevTools currently depends on this property // being called "element". const nextChildren = nextState.element; - if (nextChildren === prevChildren) { + if (nextChildren === prevChildren && !preventBailout) { // If the state is the same as before, that's a bailout because we had // no work that expires at this time. resetHydrationState(); + // @TODO need to understand if we can have an unchecked context bailout here + uncheckedContextOnBailout = false; return bailoutOnAlreadyFinishedWork( current, workInProgress, @@ -1967,26 +2008,33 @@ function updateContextProvider( } } - pushProvider(workInProgress, newValue); + const changedBits = + oldProps !== null + ? calculateChangedBits(context, newValue, oldProps.value) + : MAX_SIGNED_31_BIT_INT; + + pushProvider(workInProgress, newValue, changedBits); if (oldProps !== null) { - const oldValue = oldProps.value; - const changedBits = calculateChangedBits(context, newValue, oldValue); if (changedBits === 0) { // No change. Bailout early if children are the same. if ( oldProps.children === newProps.children && - !hasLegacyContextChanged() + !hasLegacyContextChanged() && + !preventBailout ) { + // Providers cannot read contexts so we can declare this fiber has checked + // context dependencies without actually checking + uncheckedContextOnBailout = false; return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } - } else { + } else if (!enableLazyContextPropagation) { // The context value changed. Search for matching consumers and schedule - // them to update. + // only propagateContextValue when not using the unified propagation flag propagateContextChange( workInProgress, context, @@ -2116,6 +2164,32 @@ export function markWorkInProgressReceivedUpdate() { didReceiveUpdate = true; } +function resetBailout() { + preventBailout = false; + uncheckedContextOnBailout = true; +} + +function canBailout( + workInProgress: Fiber, + renderExpirationTime: ExpirationTime, +): boolean { + if (enableLazyContextPropagation) { + uncheckedContextOnBailout = false; + if (preventBailout) { + return false; + } + if (workInProgress.propagationSigil === currentPropagationSigil()) { + return true; + } + preventBailout = checkContextDependencies( + workInProgress, + renderExpirationTime, + ); + return !preventBailout; + } + return true; +} + function bailoutOnAlreadyFinishedWork( current: Fiber | null, workInProgress: Fiber, @@ -2123,6 +2197,14 @@ function bailoutOnAlreadyFinishedWork( ): Fiber | null { cancelWorkTimer(workInProgress); + if (enableLazyContextPropagation) { + invariant( + uncheckedContextOnBailout === false, + 'work bailed out without checking context dependencies. This error is likely caused by a bug in ' + + 'React. Please file an issue.', + ); + } + if (current !== null) { // Reuse previous context list workInProgress.contextDependencies = current.contextDependencies; @@ -2133,9 +2215,20 @@ function bailoutOnAlreadyFinishedWork( stopProfilerTimerIfRunning(workInProgress); } + if ( + enableLazyContextPropagation && + workInProgress.childExpirationTime < renderExpirationTime + ) { + // if we are otherwise going to skip children, propagate context changes + // to them first in case more work is required + let child = workInProgress.child; + if (child && child.propagationSigil !== currentPropagationSigil()) { + propagateContexts(workInProgress, renderExpirationTime); + } + } + // Check if the children have any pending work. - const childExpirationTime = workInProgress.childExpirationTime; - if (childExpirationTime < renderExpirationTime) { + if (workInProgress.childExpirationTime < renderExpirationTime) { // The children don't have any work either. We can skip them. // TODO: Once we add back resuming, we should check if the children are // a work-in-progress set. If so, we need to transfer their effects. @@ -2215,7 +2308,10 @@ function beginWork( workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { - const updateExpirationTime = workInProgress.expirationTime; + // on work start we assume we have not checked contexts before bailout + resetBailout(); + + let updateExpirationTime = workInProgress.expirationTime; if (__DEV__) { if (workInProgress._debugNeedsRemount && current !== null) { @@ -2248,7 +2344,10 @@ function beginWork( // If props or context changed, mark the fiber as having performed work. // This may be unset if the props are determined to be equal later (memo). didReceiveUpdate = true; - } else if (updateExpirationTime < renderExpirationTime) { + } else if ( + updateExpirationTime < renderExpirationTime && + canBailout(workInProgress, renderExpirationTime) + ) { didReceiveUpdate = false; // This fiber does not have any pending work. Bailout without entering // the begin phase. There's still some bookkeeping we that needs to be done @@ -2285,7 +2384,7 @@ function beginWork( break; case ContextProvider: { const newValue = workInProgress.memoizedProps.value; - pushProvider(workInProgress, newValue); + pushProvider(workInProgress, newValue, 0); break; } case Profiler: diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index c0b10fabbd5fb..bb833fe104262 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -43,7 +43,10 @@ import { } from 'react-reconciler/src/ReactUpdateQueue'; import {NoWork} from './ReactFiberExpirationTime'; import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork'; -import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags'; +import { + enableSuspenseServerRenderer, + enableLazyContextPropagation, +} from 'shared/ReactFeatureFlags'; const valueCursor: StackCursor = createCursor(null); @@ -82,9 +85,60 @@ export function exitDisallowedContextReadInDEV(): void { } } -export function pushProvider(providerFiber: Fiber, nextValue: T): void { +let contextSet: Set> = new Set(); +let propagationSigil = null; +let propagationHasChangedBits = false; + +export function currentPropagationSigil(): mixed { + return propagationSigil; +} + +function someChangedBits(): boolean { + let iter = contextSet.values(); + let step = iter.next(); + for (; !step.done; step = iter.next()) { + const context = step.value; + if (context._currentChangedBits > 0) { + return true; + } + } + return false; +} + +export function pushProvider( + providerFiber: Fiber, + nextValue: T, + nextChangedBits: number, +): void { const context: ReactContext = providerFiber.type._context; + if (enableLazyContextPropagation) { + // put the context in the set of contexts for use in computing whether + // changedBits exist for current suite of contexts + contextSet.add(context); + + let currentChangedBits = context._currentChangedBits; + // update propagationHasChangedBits. only do full check if nextChangedBits + // is zero and currentChangedBits is greater than zero. Otherwise can can + // infer without checking each context + let nextPropagationHasChangedBits = + nextChangedBits > 0 || + (currentChangedBits > 0 && someChangedBits()) || + propagationHasChangedBits; + + // set next changed bits on the context + push(valueCursor, currentChangedBits, providerFiber); + context._currentChangedBits = nextChangedBits; + + // set next propagationHasChangedBits + push(valueCursor, propagationHasChangedBits, providerFiber); + propagationHasChangedBits = nextPropagationHasChangedBits; + + // create a new propagationSigil and save the previous one + push(valueCursor, propagationSigil, providerFiber); + propagationSigil = {}; + } + if (isPrimaryRenderer) { push(valueCursor, context._currentValue, providerFiber); @@ -118,7 +172,6 @@ export function pushProvider(providerFiber: Fiber, nextValue: T): void { export function popProvider(providerFiber: Fiber): void { const currentValue = valueCursor.current; - pop(valueCursor, providerFiber); const context: ReactContext = providerFiber.type._context; @@ -127,6 +180,20 @@ export function popProvider(providerFiber: Fiber): void { } else { context._currentValue2 = currentValue; } + + if (enableLazyContextPropagation) { + // restore previous propagationSigil + propagationSigil = valueCursor.current; + pop(valueCursor, providerFiber); + + // restore previous propagationHasChangedBits + propagationHasChangedBits = valueCursor.current; + pop(valueCursor, providerFiber); + + // pop changedBits value + context._currentChangedBits = valueCursor.current; + pop(valueCursor, providerFiber); + } } export function calculateChangedBits( @@ -186,6 +253,184 @@ function scheduleWorkOnParentPath( } } +export function checkContextDependencies( + fiber: Fiber, + renderExpirationTime: ExpirationTime, +): boolean { + if (enableLazyContextPropagation && propagationHasChangedBits) { + const list = fiber.contextDependencies; + if (list != null) { + let dependency = list.first; + while (dependency !== null) { + // Check if dependency bits have changed for context + let context = dependency.context; + let observedBits = dependency.observedBits; + if ((observedBits & context._currentChangedBits) !== 0) { + return true; + } + dependency = dependency.next; + } + } + } + return false; +} + +export function updateFromContextDependencies( + fiber: Fiber, + renderExpirationTime: ExpirationTime, +): boolean { + if (enableLazyContextPropagation) { + let alternate = fiber.alternate; + + // mark fiber propagationSigil + fiber.propagationSigil = propagationSigil; + if (alternate !== null) { + alternate.propagationSigil = propagationSigil; + } + + let requiresUpdate = checkContextDependencies(fiber); + + if (requiresUpdate) { + if (fiber.tag === ClassComponent) { + // Schedule a force update on the work-in-progress. + const update = createUpdate(renderExpirationTime, null); + update.tag = ForceUpdate; + // TODO: Because we don't have a work-in-progress, this will add the + // update to the current fiber, too, which means it will persist even if + // this render is thrown away. Since it's a race condition, not sure it's + // worth fixing. + enqueueUpdate(fiber, update); + } + + if (fiber.expirationTime < renderExpirationTime) { + fiber.expirationTime = renderExpirationTime; + } + if ( + alternate !== null && + alternate.expirationTime < renderExpirationTime + ) { + alternate.expirationTime = renderExpirationTime; + } + + scheduleWorkOnParentPath(fiber.return, renderExpirationTime); + + // Mark the expiration time on the list, too. + const list = fiber.contextDependencies; + if (list.expirationTime < renderExpirationTime) { + list.expirationTime = renderExpirationTime; + } + + // Since we already found a match, we can stop traversing the + // dependency list. + return true; + } + } + return false; +} + +export function propagateContexts( + workInProgress: Fiber, + renderExpirationTime: ExpirationTime, +): void { + // no need to propagate if no context values have not changed + if (propagationHasChangedBits === false) { + return; + } + let fiber = workInProgress.child; + if (fiber !== null) { + // Set the return pointer of the child to the work-in-progress fiber. + fiber.return = workInProgress; + } + while (fiber !== null) { + let nextFiber; + + let alternate = fiber.alternate; + + // Visit this fiber + let didUpdateFromContext = updateFromContextDependencies( + fiber, + renderExpirationTime, + ); + + if (didUpdateFromContext) { + // fiber required work based on it's context dependencies. do not go deeper + nextFiber = null; + } else if ( + fiber.expirationTime >= renderExpirationTime || + fiber.childExpirationTime >= renderExpirationTime + ) { + // this fiber or a descendent are already scheduled for work. + // on to siblings + nextFiber = null; + } else if (fiber.tag === ContextProvider) { + // Don't scan deeper since this is a ContextProvider + // schedule work on Provider + if (fiber.expirationTime < renderExpirationTime) { + fiber.expirationTime = renderExpirationTime; + } + if ( + alternate !== null && + alternate.expirationTime < renderExpirationTime + ) { + alternate.expirationTime = renderExpirationTime; + } + scheduleWorkOnParentPath(fiber.return, renderExpirationTime); + // don't go deeper, visit siblings if any + nextFiber = null; + } else if ( + enableSuspenseServerRenderer && + fiber.tag === DehydratedSuspenseComponent + ) { + // If a dehydrated suspense component is in this subtree, we don't know + // if it will have any context consumers in it. The best we can do is + // mark it as having updates on its children. + if (fiber.expirationTime < renderExpirationTime) { + fiber.expirationTime = renderExpirationTime; + } + if ( + alternate !== null && + alternate.expirationTime < renderExpirationTime + ) { + alternate.expirationTime = renderExpirationTime; + } + // This is intentionally passing this fiber as the parent + // because we want to schedule this fiber as having work + // on its children. We'll use the childExpirationTime on + // this fiber to indicate that a context has changed. + scheduleWorkOnParentPath(fiber, renderExpirationTime); + nextFiber = fiber.sibling; + } else { + // Traverse down. + nextFiber = fiber.child; + } + + if (nextFiber !== null) { + // Set the return pointer of the child to the work-in-progress fiber. + nextFiber.return = fiber; + } else { + // No child. Traverse to next sibling. + nextFiber = fiber; + while (nextFiber !== null) { + if (nextFiber === workInProgress) { + // We're back to the root of this subtree. Exit. + nextFiber = null; + break; + } + let sibling = nextFiber.sibling; + if (sibling !== null) { + // Set the return pointer of the sibling to the work-in-progress fiber. + sibling.return = nextFiber.return; + nextFiber = sibling; + break; + } + // No more siblings. Traverse up. + nextFiber = nextFiber.return; + } + } + fiber = nextFiber; + } +} + export function propagateContextChange( workInProgress: Fiber, context: ReactContext, diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js index e04b75dc593a1..cee0f05959bf2 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js @@ -1049,6 +1049,142 @@ describe('ReactNewContext', () => { span(2), ]); }); + describe('stress test', () => { + it('controlled lots of contexts', () => { + let ContextA = React.createContext(0); + let ConsumerA = getConsumer(ContextA); + + let ContextB = React.createContext(0); + let ConsumerB = getConsumer(ContextB); + + let ContextC = React.createContext(0); + let ConsumerC = getConsumer(ContextC); + + class Indirection extends React.Component { + shouldComponentUpdate() { + return false; + } + render() { + return this.props.children; + } + } + + const Foo = React.memo(({consumer, depth, name, children}) => { + let Consumer = consumer; + + if (typeof depth !== 'number' || depth <= 1) { + return ( + + {value => } + + ); + } else { + return ( + + + {_ => ( + + {children} + + )} + + + ); + } + }); + + const Yield = ({name, value}) => { + let output = `${name}: ${value}`; + Scheduler.yieldValue(output); + return {output}; + }; + + function App(props) { + return ( + + + + + + + + + + ); + } + + for (let i = 0; i < 25; i++) { + // each individually + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A: 0', 'B: 0', 'C: 0']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A: 1']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['B: 1']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['C: 1']); + // two at a time + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A: 2', 'B: 2']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['B: 3', 'C: 3']); + // all at once + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A: 4', 'B: 4', 'C: 4']); + } + }); + it('non-context stress test', () => { + const Foo = React.memo(({depth, name, children}) => { + if (typeof depth !== 'number' || depth <= 1) { + return ; + } else { + return ( + + {children} + + ); + } + }); + + const Yield = ({name}) => { + let output = `${name}`; + Scheduler.yieldValue(output); + return {output}; + }; + + function App(props) { + return ( +
+ + + +
+ ); + } + + for (let i = 0; i < 50; i++) { + // each individually + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + ReactNoop.render(); + expect(Scheduler).toFlushAndYield(['A', 'B', 'C']); + } + }); + }); }); } diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index 643a13019e074..d28ccf2aa438b 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -42,6 +42,7 @@ export function createContext( // Secondary renderers store their context values on separate fields. _currentValue: defaultValue, _currentValue2: defaultValue, + _currentChangedBits: 0, // Used to track how many concurrent renderers this context currently // supports within in a single renderer. Such as parallel server rendering. _threadCount: 0, diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index f33ef13e8ac54..c3476ab1b8aa5 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -75,3 +75,6 @@ export const revertPassiveEffectsChange = false; // but without making them discrete. The flag exists in case it causes // starvation problems. export const enableUserBlockingEvents = false; + +// Alternate Context Propagation algorithm, variation that propagates all contexts together +export const enableLazyContextPropagation = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 1395754de0848..84fe845f72c3e 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -36,6 +36,7 @@ export const enableJSXTransformAPI = false; export const warnAboutMissingMockScheduler = true; export const revertPassiveEffectsChange = false; export const enableUserBlockingEvents = false; +export const enableLazyContextPropagation = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index cbd417aa086ab..8307a31218f8c 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -31,6 +31,7 @@ export const enableJSXTransformAPI = false; export const warnAboutMissingMockScheduler = false; export const revertPassiveEffectsChange = false; export const enableUserBlockingEvents = false; +export const enableLazyContextPropagation = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index fd7086c247122..63c430f038931 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -31,6 +31,7 @@ export const enableJSXTransformAPI = false; export const warnAboutMissingMockScheduler = true; export const revertPassiveEffectsChange = false; export const enableUserBlockingEvents = false; +export const enableLazyContextPropagation = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index fc30a865ec046..c7074e5fe8f4a 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -31,6 +31,7 @@ export const enableJSXTransformAPI = false; export const warnAboutMissingMockScheduler = false; export const revertPassiveEffectsChange = false; export const enableUserBlockingEvents = false; +export const enableLazyContextPropagation = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index be456ba14257d..25d2f15cddbd6 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -31,6 +31,7 @@ export const enableEventAPI = true; export const enableJSXTransformAPI = true; export const warnAboutMissingMockScheduler = true; export const enableUserBlockingEvents = false; +export const enableLazyContextPropagation = true; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index b8328ac7c57b4..89cc0090af322 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -21,6 +21,7 @@ export const { warnAboutDeprecatedSetNativeProps, revertPassiveEffectsChange, enableUserBlockingEvents, + enableLazyContextPropagation, } = require('ReactFeatureFlags'); // In www, we have experimental support for gathering data