From 94eed63c49d989861ae7cd62e111de6d717f0a10 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 25 Apr 2024 10:40:40 -0700 Subject: [PATCH] (Land #28798) Move Current Owner (and Cache) to an Async Dispatcher (#28912) Rebasing and landing https://github.com/facebook/react/pull/28798 This PR was approved already but held back to give time for the sync. Rebased and landing here without pushing to seb's remote to avoid possibility of lost updates --------- Co-authored-by: Sebastian Markbage --- .../__tests__/ReactCompositeComponent-test.js | 27 +++++-- .../react-dom/src/client/ReactDOMRootFB.js | 4 +- .../src/ReactNativePublicCompat.js | 6 +- ...rCache.js => ReactFiberAsyncDispatcher.js} | 16 +++- .../src/ReactFiberBeginWork.js | 10 +-- .../src/ReactFiberCurrentOwner.js | 16 ++++ .../src/ReactFiberTreeReflection.js | 4 +- .../src/ReactFiberWorkLoop.js | 35 ++++----- .../src/ReactInternalTypes.js | 4 +- .../src/ReactFizzAsyncDispatcher.js | 27 +++++++ packages/react-server/src/ReactFizzCache.js | 18 ----- packages/react-server/src/ReactFizzServer.js | 13 ++-- .../react-server/src/ReactFlightServer.js | 18 +++-- .../src/flight/ReactFlightAsyncDispatcher.js | 54 ++++++++++++++ .../src/flight/ReactFlightServerCache.js | 33 --------- .../src/ReactSuspenseTestUtils.js | 13 ++-- packages/react/src/ReactCacheImpl.js | 2 +- packages/react/src/ReactHooks.js | 2 +- .../react/src/ReactSharedInternalsClient.js | 16 +--- .../react/src/ReactSharedInternalsServer.js | 16 ++-- packages/react/src/jsx/ReactJSXElement.js | 73 ++++++++----------- 21 files changed, 231 insertions(+), 176 deletions(-) rename packages/react-reconciler/src/{ReactFiberCache.js => ReactFiberAsyncDispatcher.js} (68%) create mode 100644 packages/react-reconciler/src/ReactFiberCurrentOwner.js create mode 100644 packages/react-server/src/ReactFizzAsyncDispatcher.js delete mode 100644 packages/react-server/src/ReactFizzCache.js create mode 100644 packages/react-server/src/flight/ReactFlightAsyncDispatcher.js delete mode 100644 packages/react-server/src/flight/ReactFlightServerCache.js diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 4630f9ddbc163..894afb39ba0cf 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -537,16 +537,24 @@ describe('ReactCompositeComponent', () => { }); it('should cleanup even if render() fatals', async () => { + const dispatcherEnabled = + __DEV__ || + !gate(flags => flags.disableStringRefs) || + gate(flags => flags.enableCache); + const ownerEnabled = __DEV__ || !gate(flags => flags.disableStringRefs); + + let stashedDispatcher; class BadComponent extends React.Component { render() { + // Stash the dispatcher that was available in render so we can check + // that its internals also reset. + stashedDispatcher = ReactSharedInternals.A; throw new Error(); } } const instance = ; - expect(ReactSharedInternals.owner).toBe( - __DEV__ || !gate(flags => flags.disableStringRefs) ? null : undefined, - ); + expect(ReactSharedInternals.A).toBe(dispatcherEnabled ? null : undefined); const root = ReactDOMClient.createRoot(document.createElement('div')); await expect(async () => { @@ -555,9 +563,16 @@ describe('ReactCompositeComponent', () => { }); }).rejects.toThrow(); - expect(ReactSharedInternals.owner).toBe( - __DEV__ || !gate(flags => flags.disableStringRefs) ? null : undefined, - ); + expect(ReactSharedInternals.A).toBe(dispatcherEnabled ? null : undefined); + if (dispatcherEnabled) { + if (ownerEnabled) { + expect(stashedDispatcher.getOwner()).toBe(null); + } else { + expect(stashedDispatcher.getOwner).toBe(undefined); + } + } else { + expect(stashedDispatcher).toBe(undefined); + } }); it('should call componentWillUnmount before unmounting', async () => { diff --git a/packages/react-dom/src/client/ReactDOMRootFB.js b/packages/react-dom/src/client/ReactDOMRootFB.js index f41d8868279c7..0aa2306665f4e 100644 --- a/packages/react-dom/src/client/ReactDOMRootFB.js +++ b/packages/react-dom/src/client/ReactDOMRootFB.js @@ -61,7 +61,7 @@ import {LegacyRoot} from 'react-reconciler/src/ReactRootTags'; import getComponentNameFromType from 'shared/getComponentNameFromType'; import {has as hasInstance} from 'shared/ReactInstanceMap'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; +import {currentOwner} from 'react-reconciler/src/ReactFiberCurrentOwner'; import assign from 'shared/assign'; @@ -342,7 +342,7 @@ export function findDOMNode( componentOrElement: Element | ?React$Component, ): null | Element | Text { if (__DEV__) { - const owner = (ReactSharedInternals.owner: any); + const owner = currentOwner; if (owner !== null && owner.stateNode !== null) { const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender; if (!warnedAboutRefsInRender) { diff --git a/packages/react-native-renderer/src/ReactNativePublicCompat.js b/packages/react-native-renderer/src/ReactNativePublicCompat.js index 93b85e172d414..081ea6f7eb571 100644 --- a/packages/react-native-renderer/src/ReactNativePublicCompat.js +++ b/packages/react-native-renderer/src/ReactNativePublicCompat.js @@ -24,14 +24,14 @@ import { findHostInstanceWithWarning, } from 'react-reconciler/src/ReactFiberReconciler'; import {doesFiberContain} from 'react-reconciler/src/ReactFiberTreeReflection'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; import getComponentNameFromType from 'shared/getComponentNameFromType'; +import {currentOwner} from 'react-reconciler/src/ReactFiberCurrentOwner'; export function findHostInstance_DEPRECATED( componentOrHandle: ?(ElementRef | number), ): ?ElementRef> { if (__DEV__) { - const owner = ReactSharedInternals.owner; + const owner = currentOwner; if (owner !== null && owner.stateNode !== null) { if (!owner.stateNode._warnedAboutRefsInRender) { console.error( @@ -86,7 +86,7 @@ export function findHostInstance_DEPRECATED( export function findNodeHandle(componentOrHandle: any): ?number { if (__DEV__) { - const owner = ReactSharedInternals.owner; + const owner = currentOwner; if (owner !== null && owner.stateNode !== null) { if (!owner.stateNode._warnedAboutRefsInRender) { console.error( diff --git a/packages/react-reconciler/src/ReactFiberCache.js b/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js similarity index 68% rename from packages/react-reconciler/src/ReactFiberCache.js rename to packages/react-reconciler/src/ReactFiberAsyncDispatcher.js index 3f465d8b8837c..f1c251965512c 100644 --- a/packages/react-reconciler/src/ReactFiberCache.js +++ b/packages/react-reconciler/src/ReactFiberAsyncDispatcher.js @@ -7,13 +7,17 @@ * @flow */ -import type {CacheDispatcher} from './ReactInternalTypes'; +import type {AsyncDispatcher, Fiber} from './ReactInternalTypes'; import type {Cache} from './ReactFiberCacheComponent'; import {enableCache} from 'shared/ReactFeatureFlags'; import {readContext} from './ReactFiberNewContext'; import {CacheContext} from './ReactFiberCacheComponent'; +import {disableStringRefs} from 'shared/ReactFeatureFlags'; + +import {currentOwner} from './ReactFiberCurrentOwner'; + function getCacheForType(resourceType: () => T): T { if (!enableCache) { throw new Error('Not implemented.'); @@ -27,6 +31,12 @@ function getCacheForType(resourceType: () => T): T { return cacheForType; } -export const DefaultCacheDispatcher: CacheDispatcher = { +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ getCacheForType, -}; +}: any); + +if (__DEV__ || !disableStringRefs) { + DefaultAsyncDispatcher.getOwner = (): null | Fiber => { + return currentOwner; + }; +} diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 034c853976aa8..b1d639583df7f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -91,7 +91,6 @@ import { Passive, DidDefer, } from './ReactFiberFlags'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; import { debugRenderPhaseSideEffectsForStrictMode, disableLegacyContext, @@ -297,6 +296,7 @@ import { pushRootMarkerInstance, TransitionTracingMarker, } from './ReactFiberTracingMarkerComponent'; +import {setCurrentOwner} from './ReactFiberCurrentOwner'; // A special exception that's used to unwind the stack when an update flows // into a dehydrated boundary. @@ -432,7 +432,7 @@ function updateForwardRef( markComponentRenderStarted(workInProgress); } if (__DEV__) { - ReactSharedInternals.owner = workInProgress; + setCurrentOwner(workInProgress); setIsRendering(true); nextChildren = renderWithHooks( current, @@ -1150,7 +1150,7 @@ function updateFunctionComponent( markComponentRenderStarted(workInProgress); } if (__DEV__) { - ReactSharedInternals.owner = workInProgress; + setCurrentOwner(workInProgress); setIsRendering(true); nextChildren = renderWithHooks( current, @@ -1373,7 +1373,7 @@ function finishClassComponent( // Rerender if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = workInProgress; + setCurrentOwner(workInProgress); } let nextChildren; if ( @@ -3419,7 +3419,7 @@ function updateContextConsumer( } let newChildren; if (__DEV__) { - ReactSharedInternals.owner = workInProgress; + setCurrentOwner(workInProgress); setIsRendering(true); newChildren = render(newValue); setIsRendering(false); diff --git a/packages/react-reconciler/src/ReactFiberCurrentOwner.js b/packages/react-reconciler/src/ReactFiberCurrentOwner.js new file mode 100644 index 0000000000000..0c1f3d6e3130d --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberCurrentOwner.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Fiber} from './ReactInternalTypes'; + +export let currentOwner: Fiber | null = null; + +export function setCurrentOwner(fiber: null | Fiber) { + currentOwner = fiber; +} diff --git a/packages/react-reconciler/src/ReactFiberTreeReflection.js b/packages/react-reconciler/src/ReactFiberTreeReflection.js index 64cc35d2c0bf4..57640bd7f3b18 100644 --- a/packages/react-reconciler/src/ReactFiberTreeReflection.js +++ b/packages/react-reconciler/src/ReactFiberTreeReflection.js @@ -12,7 +12,6 @@ import type {Container, SuspenseInstance} from './ReactFiberConfig'; import type {SuspenseState} from './ReactFiberSuspenseComponent'; import {get as getInstance} from 'shared/ReactInstanceMap'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; import { ClassComponent, @@ -25,6 +24,7 @@ import { SuspenseComponent, } from './ReactWorkTags'; import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; +import {currentOwner} from './ReactFiberCurrentOwner'; export function getNearestMountedFiber(fiber: Fiber): null | Fiber { let node = fiber; @@ -89,7 +89,7 @@ export function isFiberMounted(fiber: Fiber): boolean { export function isMounted(component: React$Component): boolean { if (__DEV__) { - const owner = (ReactSharedInternals.owner: any); + const owner = currentOwner; if (owner !== null && owner.tag === ClassComponent) { const ownerFiber: Fiber = owner; const instance = ownerFiber.stateNode; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 8b0c8729506cc..00391bb354ee8 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -203,7 +203,8 @@ import { resetHooksOnUnwind, ContextOnlyDispatcher, } from './ReactFiberHooks'; -import {DefaultCacheDispatcher} from './ReactFiberCache'; +import {DefaultAsyncDispatcher} from './ReactFiberAsyncDispatcher'; +import {setCurrentOwner} from './ReactFiberCurrentOwner'; import { createCapturedValueAtFiber, type CapturedValue, @@ -1684,7 +1685,7 @@ function handleThrow(root: FiberRoot, thrownValue: any): void { resetHooksAfterThrow(); resetCurrentDebugFiberInDEV(); if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; + setCurrentOwner(null); } if (thrownValue === SuspenseException) { @@ -1874,19 +1875,19 @@ function popDispatcher(prevDispatcher: any) { ReactSharedInternals.H = prevDispatcher; } -function pushCacheDispatcher() { - if (enableCache) { - const prevCacheDispatcher = ReactSharedInternals.C; - ReactSharedInternals.C = DefaultCacheDispatcher; - return prevCacheDispatcher; +function pushAsyncDispatcher() { + if (enableCache || __DEV__ || !disableStringRefs) { + const prevAsyncDispatcher = ReactSharedInternals.A; + ReactSharedInternals.A = DefaultAsyncDispatcher; + return prevAsyncDispatcher; } else { return null; } } -function popCacheDispatcher(prevCacheDispatcher: any) { - if (enableCache) { - ReactSharedInternals.C = prevCacheDispatcher; +function popAsyncDispatcher(prevAsyncDispatcher: any) { + if (enableCache || __DEV__ || !disableStringRefs) { + ReactSharedInternals.A = prevAsyncDispatcher; } } @@ -1963,7 +1964,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; const prevDispatcher = pushDispatcher(root.containerInfo); - const prevCacheDispatcher = pushCacheDispatcher(); + const prevAsyncDispatcher = pushAsyncDispatcher(); // If the root or lanes have changed, throw out the existing stack // and prepare a fresh one. Otherwise we'll continue where we left off. @@ -2061,7 +2062,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { executionContext = prevExecutionContext; popDispatcher(prevDispatcher); - popCacheDispatcher(prevCacheDispatcher); + popAsyncDispatcher(prevAsyncDispatcher); if (workInProgress !== null) { // This is a sync render, so we should have finished the whole tree. @@ -2104,7 +2105,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; const prevDispatcher = pushDispatcher(root.containerInfo); - const prevCacheDispatcher = pushCacheDispatcher(); + const prevAsyncDispatcher = pushAsyncDispatcher(); // If the root or lanes have changed, throw out the existing stack // and prepare a fresh one. Otherwise we'll continue where we left off. @@ -2317,7 +2318,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { resetContextDependencies(); popDispatcher(prevDispatcher); - popCacheDispatcher(prevCacheDispatcher); + popAsyncDispatcher(prevAsyncDispatcher); executionContext = prevExecutionContext; if (__DEV__) { @@ -2386,7 +2387,7 @@ function performUnitOfWork(unitOfWork: Fiber): void { } if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; + setCurrentOwner(null); } } @@ -2501,7 +2502,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void { } if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; + setCurrentOwner(null); } } @@ -2894,7 +2895,7 @@ function commitRootImpl( // Reset this to null before calling lifecycles if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; + setCurrentOwner(null); } // The commit phase is broken into several sub-phases. We do a separate pass diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index f12b9a16c570b..6fa9142e2d5ee 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -434,6 +434,8 @@ export type Dispatcher = { ) => [Awaited, (P) => void, boolean], }; -export type CacheDispatcher = { +export type AsyncDispatcher = { getCacheForType: (resourceType: () => T) => T, + // DEV-only (or !disableStringRefs) + getOwner: () => null | Fiber | ReactComponentInfo, }; diff --git a/packages/react-server/src/ReactFizzAsyncDispatcher.js b/packages/react-server/src/ReactFizzAsyncDispatcher.js new file mode 100644 index 0000000000000..8e8cb2e219992 --- /dev/null +++ b/packages/react-server/src/ReactFizzAsyncDispatcher.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes'; + +import {disableStringRefs} from 'shared/ReactFeatureFlags'; + +function getCacheForType(resourceType: () => T): T { + throw new Error('Not implemented.'); +} + +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ + getCacheForType, +}: any); + +if (__DEV__ || !disableStringRefs) { + // Fizz never tracks owner but the JSX runtime looks for this. + DefaultAsyncDispatcher.getOwner = (): null => { + return null; + }; +} diff --git a/packages/react-server/src/ReactFizzCache.js b/packages/react-server/src/ReactFizzCache.js deleted file mode 100644 index 10ff0ed7c0d1a..0000000000000 --- a/packages/react-server/src/ReactFizzCache.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes'; - -function getCacheForType(resourceType: () => T): T { - throw new Error('Not implemented.'); -} - -export const DefaultCacheDispatcher: CacheDispatcher = { - getCacheForType, -}; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index b6925b1c7b58b..9c8207f6bfd89 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -111,7 +111,7 @@ import { getActionStateCount, getActionStateMatchingIndex, } from './ReactFizzHooks'; -import {DefaultCacheDispatcher} from './ReactFizzCache'; +import {DefaultAsyncDispatcher} from './ReactFizzAsyncDispatcher'; import {getStackByComponentStackNode} from './ReactFizzComponentStack'; import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext'; @@ -148,6 +148,7 @@ import { enableRefAsProp, disableDefaultPropsExceptForClasses, enableAsyncIterableChildren, + disableStringRefs, } from 'shared/ReactFeatureFlags'; import assign from 'shared/assign'; @@ -3791,10 +3792,10 @@ export function performWork(request: Request): void { const prevContext = getActiveContext(); const prevDispatcher = ReactSharedInternals.H; ReactSharedInternals.H = HooksDispatcher; - let prevCacheDispatcher = null; - if (enableCache) { - prevCacheDispatcher = ReactSharedInternals.C; - ReactSharedInternals.C = DefaultCacheDispatcher; + let prevAsyncDispatcher = null; + if (enableCache || __DEV__ || !disableStringRefs) { + prevAsyncDispatcher = ReactSharedInternals.A; + ReactSharedInternals.A = DefaultAsyncDispatcher; } const prevRequest = currentRequest; @@ -3826,7 +3827,7 @@ export function performWork(request: Request): void { setCurrentResumableState(prevResumableState); ReactSharedInternals.H = prevDispatcher; if (enableCache) { - ReactSharedInternals.C = prevCacheDispatcher; + ReactSharedInternals.A = prevAsyncDispatcher; } if (__DEV__) { diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index a22faf755fd65..5c66d56bf3075 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -89,7 +89,11 @@ import { getThenableStateAfterSuspending, resetHooksForRequest, } from './ReactFlightHooks'; -import {DefaultCacheDispatcher} from './flight/ReactFlightServerCache'; +import { + DefaultAsyncDispatcher, + currentOwner, + setCurrentOwner, +} from './flight/ReactFlightAsyncDispatcher'; import { getIteratorFn, @@ -158,7 +162,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) { // We don't currently use this id for anything but we emit it so that we can later // refer to previous logs in debug info to associate them with a component. const id = request.nextChunkId++; - const owner: null | ReactComponentInfo = ReactSharedInternals.owner; + const owner: null | ReactComponentInfo = currentOwner; emitConsoleChunk(request, id, methodName, owner, stack, arguments); } // $FlowFixMe[prop-missing] @@ -360,14 +364,14 @@ export function createRequest( environmentName: void | string, ): Request { if ( - ReactSharedInternals.C !== null && - ReactSharedInternals.C !== DefaultCacheDispatcher + ReactSharedInternals.A !== null && + ReactSharedInternals.A !== DefaultAsyncDispatcher ) { throw new Error( 'Currently React only supports one RSC renderer at a time.', ); } - ReactSharedInternals.C = DefaultCacheDispatcher; + ReactSharedInternals.A = DefaultAsyncDispatcher; const abortSet: Set = new Set(); const pingedTasks: Array = []; @@ -856,11 +860,11 @@ function renderFunctionComponent( const secondArg = undefined; let result; if (__DEV__) { - ReactSharedInternals.owner = componentDebugInfo; + setCurrentOwner(componentDebugInfo); try { result = Component(props, secondArg); } finally { - ReactSharedInternals.owner = null; + setCurrentOwner(null); } } else { result = Component(props, secondArg); diff --git a/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js b/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js new file mode 100644 index 0000000000000..1cdd67a82153b --- /dev/null +++ b/packages/react-server/src/flight/ReactFlightAsyncDispatcher.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {ReactComponentInfo} from 'shared/ReactTypes'; + +import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes'; + +import {resolveRequest, getCache} from '../ReactFlightServer'; + +import {disableStringRefs} from 'shared/ReactFeatureFlags'; + +function resolveCache(): Map { + const request = resolveRequest(); + if (request) { + return getCache(request); + } + return new Map(); +} + +export const DefaultAsyncDispatcher: AsyncDispatcher = ({ + getCacheForType(resourceType: () => T): T { + const cache = resolveCache(); + let entry: T | void = (cache.get(resourceType): any); + if (entry === undefined) { + entry = resourceType(); + // TODO: Warn if undefined? + cache.set(resourceType, entry); + } + return entry; + }, +}: any); + +export let currentOwner: ReactComponentInfo | null = null; + +if (__DEV__) { + DefaultAsyncDispatcher.getOwner = (): null | ReactComponentInfo => { + return currentOwner; + }; +} else if (!disableStringRefs) { + // Server Components never use string refs but the JSX runtime looks for it. + DefaultAsyncDispatcher.getOwner = (): null | ReactComponentInfo => { + return null; + }; +} + +export function setCurrentOwner(componentInfo: null | ReactComponentInfo) { + currentOwner = componentInfo; +} diff --git a/packages/react-server/src/flight/ReactFlightServerCache.js b/packages/react-server/src/flight/ReactFlightServerCache.js deleted file mode 100644 index 5c2469a2a9864..0000000000000 --- a/packages/react-server/src/flight/ReactFlightServerCache.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes'; - -import {resolveRequest, getCache} from '../ReactFlightServer'; - -function resolveCache(): Map { - const request = resolveRequest(); - if (request) { - return getCache(request); - } - return new Map(); -} - -export const DefaultCacheDispatcher: CacheDispatcher = { - getCacheForType(resourceType: () => T): T { - const cache = resolveCache(); - let entry: T | void = (cache.get(resourceType): any); - if (entry === undefined) { - entry = resourceType(); - // TODO: Warn if undefined? - cache.set(resourceType, entry); - } - return entry; - }, -}; diff --git a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js index b3bc53006d771..bc2d4bee79d02 100644 --- a/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js +++ b/packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js @@ -7,12 +7,12 @@ * @flow */ -import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes'; +import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes'; import ReactSharedInternals from 'shared/ReactSharedInternals'; export function waitForSuspense(fn: () => T): Promise { const cache: Map = new Map(); - const testDispatcher: CacheDispatcher = { + const testDispatcher: AsyncDispatcher = { getCacheForType(resourceType: () => R): R { let entry: R | void = (cache.get(resourceType): any); if (entry === undefined) { @@ -22,12 +22,15 @@ export function waitForSuspense(fn: () => T): Promise { } return entry; }, + getOwner(): null { + return null; + }, }; // Not using async/await because we don't compile it. return new Promise((resolve, reject) => { function retry() { - const prevDispatcher = ReactSharedInternals.C; - ReactSharedInternals.C = testDispatcher; + const prevDispatcher = ReactSharedInternals.A; + ReactSharedInternals.A = testDispatcher; try { const result = fn(); resolve(result); @@ -38,7 +41,7 @@ export function waitForSuspense(fn: () => T): Promise { reject(thrownValue); } } finally { - ReactSharedInternals.C = prevDispatcher; + ReactSharedInternals.A = prevDispatcher; } } retry(); diff --git a/packages/react/src/ReactCacheImpl.js b/packages/react/src/ReactCacheImpl.js index 99a3809bc9372..cc3136897e350 100644 --- a/packages/react/src/ReactCacheImpl.js +++ b/packages/react/src/ReactCacheImpl.js @@ -54,7 +54,7 @@ function createCacheNode(): CacheNode { export function cache, T>(fn: (...A) => T): (...A) => T { return function () { - const dispatcher = ReactSharedInternals.C; + const dispatcher = ReactSharedInternals.A; if (!dispatcher) { // If there is no dispatcher, then we treat this as not being cached. // $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code. diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 79513656cea13..93d9fa28f07f9 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -44,7 +44,7 @@ function resolveDispatcher() { } export function getCacheForType(resourceType: () => T): T { - const dispatcher = ReactSharedInternals.C; + const dispatcher = ReactSharedInternals.A; if (!dispatcher) { // If there is no dispatcher, then we treat this as not being cached. return resourceType(); diff --git a/packages/react/src/ReactSharedInternalsClient.js b/packages/react/src/ReactSharedInternalsClient.js index ea57f2fcdcc7f..52adec8be4b30 100644 --- a/packages/react/src/ReactSharedInternalsClient.js +++ b/packages/react/src/ReactSharedInternalsClient.js @@ -8,19 +8,15 @@ */ import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes'; -import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes'; +import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes'; import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent'; -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; - -import {disableStringRefs} from 'shared/ReactFeatureFlags'; export type SharedStateClient = { H: null | Dispatcher, // ReactCurrentDispatcher for Hooks - C: null | CacheDispatcher, // ReactCurrentCache for Cache + A: null | AsyncDispatcher, // ReactCurrentCache for Cache T: null | BatchConfigTransition, // ReactCurrentBatchConfig for Transitions - // DEV-only-ish - owner: null | Fiber, // ReactCurrentOwner is Fiber on the Client, null in Fizz. Flight uses SharedStateServer. + // DEV-only // ReactCurrentActQueue actQueue: null | Array, @@ -47,14 +43,10 @@ export type RendererTask = boolean => RendererTask | null; const ReactSharedInternals: SharedStateClient = ({ H: null, - C: null, + A: null, T: null, }: any); -if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; -} - if (__DEV__) { ReactSharedInternals.actQueue = null; ReactSharedInternals.isBatchingLegacy = false; diff --git a/packages/react/src/ReactSharedInternalsServer.js b/packages/react/src/ReactSharedInternalsServer.js index be5dd91d2146b..4c63c75a0d5d2 100644 --- a/packages/react/src/ReactSharedInternalsServer.js +++ b/packages/react/src/ReactSharedInternalsServer.js @@ -8,8 +8,7 @@ */ import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes'; -import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes'; -import type {ReactComponentInfo} from 'shared/ReactTypes'; +import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes'; import type { Reference, @@ -24,11 +23,11 @@ import { TaintRegistryPendingRequests, } from './ReactTaintRegistry'; -import {disableStringRefs, enableTaint} from 'shared/ReactFeatureFlags'; +import {enableTaint} from 'shared/ReactFeatureFlags'; export type SharedStateServer = { H: null | Dispatcher, // ReactCurrentDispatcher for Hooks - C: null | CacheDispatcher, // ReactCurrentCache for Cache + A: null | AsyncDispatcher, // ReactCurrentCache for Cache // enableTaint TaintRegistryObjects: WeakMap, @@ -36,8 +35,7 @@ export type SharedStateServer = { TaintRegistryByteLengths: Set, TaintRegistryPendingRequests: Set, - // DEV-only-ish - owner: null | ReactComponentInfo, // ReactCurrentOwner is ReactComponentInfo in Flight, null in Fizz. Fiber/Fizz uses SharedStateClient. + // DEV-only // ReactDebugCurrentFrame setExtraStackFrame: (stack: null | string) => void, @@ -49,7 +47,7 @@ export type RendererTask = boolean => RendererTask | null; const ReactSharedInternals: SharedStateServer = ({ H: null, - C: null, + A: null, }: any); if (enableTaint) { @@ -60,10 +58,6 @@ if (enableTaint) { TaintRegistryPendingRequests; } -if (__DEV__ || !disableStringRefs) { - ReactSharedInternals.owner = null; -} - if (__DEV__) { let currentExtraStackFrame = (null: null | string); ReactSharedInternals.setExtraStackFrame = function (stack: null | string) { diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js index 81f50489204f0..d8452a2e360a2 100644 --- a/packages/react/src/jsx/ReactJSXElement.js +++ b/packages/react/src/jsx/ReactJSXElement.js @@ -29,6 +29,17 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference'); +function getOwner() { + if (__DEV__ || !disableStringRefs) { + const dispatcher = ReactSharedInternals.A; + if (dispatcher === null) { + return null; + } + return dispatcher.getOwner(); + } + return null; +} + let specialPropKeyWarningShown; let specialPropRefWarningShown; let didWarnAboutStringRefs; @@ -66,16 +77,15 @@ function hasValidKey(config) { function warnIfStringRefCannotBeAutoConverted(config, self) { if (__DEV__) { + let owner; if ( !disableStringRefs && typeof config.ref === 'string' && - ReactSharedInternals.owner && + (owner = getOwner()) && self && - ReactSharedInternals.owner.stateNode !== self + owner.stateNode !== self ) { - const componentName = getComponentNameFromType( - ReactSharedInternals.owner.type, - ); + const componentName = getComponentNameFromType(owner.type); if (!didWarnAboutStringRefs[componentName]) { console.error( @@ -85,7 +95,7 @@ function warnIfStringRefCannotBeAutoConverted(config, self) { 'We ask you to manually fix this case by using useRef() or createRef() instead. ' + 'Learn more about using refs safely here: ' + 'https://react.dev/link/strict-mode-string-ref', - getComponentNameFromType(ReactSharedInternals.owner.type), + getComponentNameFromType(owner.type), config.ref, ); didWarnAboutStringRefs[componentName] = true; @@ -339,7 +349,7 @@ export function jsxProd(type, config, maybeKey) { if (!enableRefAsProp) { ref = config.ref; if (!disableStringRefs) { - ref = coerceStringRef(ref, ReactSharedInternals.owner, type); + ref = coerceStringRef(ref, getOwner(), type); } } } @@ -365,11 +375,7 @@ export function jsxProd(type, config, maybeKey) { // Skip over reserved prop names if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) { if (enableRefAsProp && !disableStringRefs && propName === 'ref') { - props.ref = coerceStringRef( - config[propName], - ReactSharedInternals.owner, - type, - ); + props.ref = coerceStringRef(config[propName], getOwner(), type); } else { props[propName] = config[propName]; } @@ -389,15 +395,7 @@ export function jsxProd(type, config, maybeKey) { } } - return ReactElement( - type, - key, - ref, - undefined, - undefined, - ReactSharedInternals.owner, - props, - ); + return ReactElement(type, key, ref, undefined, undefined, getOwner(), props); } // While `jsxDEV` should never be called when running in production, we do @@ -571,7 +569,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) { if (!enableRefAsProp) { ref = config.ref; if (!disableStringRefs) { - ref = coerceStringRef(ref, ReactSharedInternals.owner, type); + ref = coerceStringRef(ref, getOwner(), type); } } if (!disableStringRefs) { @@ -600,11 +598,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) { // Skip over reserved prop names if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) { if (enableRefAsProp && !disableStringRefs && propName === 'ref') { - props.ref = coerceStringRef( - config[propName], - ReactSharedInternals.owner, - type, - ); + props.ref = coerceStringRef(config[propName], getOwner(), type); } else { props[propName] = config[propName]; } @@ -643,7 +637,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) { ref, self, source, - ReactSharedInternals.owner, + getOwner(), props, ); @@ -747,7 +741,7 @@ export function createElement(type, config, children) { if (!enableRefAsProp) { ref = config.ref; if (!disableStringRefs) { - ref = coerceStringRef(ref, ReactSharedInternals.owner, type); + ref = coerceStringRef(ref, getOwner(), type); } } @@ -777,11 +771,7 @@ export function createElement(type, config, children) { propName !== '__source' ) { if (enableRefAsProp && !disableStringRefs && propName === 'ref') { - props.ref = coerceStringRef( - config[propName], - ReactSharedInternals.owner, - type, - ); + props.ref = coerceStringRef(config[propName], getOwner(), type); } else { props[propName] = config[propName]; } @@ -837,7 +827,7 @@ export function createElement(type, config, children) { ref, undefined, undefined, - ReactSharedInternals.owner, + getOwner(), props, ); @@ -887,7 +877,7 @@ export function cloneElement(element, config, children) { if (config != null) { if (hasValidRef(config)) { - owner = ReactSharedInternals.owner; + owner = __DEV__ || !disableStringRefs ? getOwner() : undefined; if (!enableRefAsProp) { // Silently steal the ref from the parent. ref = config.ref; @@ -981,8 +971,9 @@ export function cloneElement(element, config, children) { function getDeclarationErrorAddendum() { if (__DEV__) { - if (ReactSharedInternals.owner) { - const name = getComponentNameFromType(ReactSharedInternals.owner.type); + const owner = getOwner(); + if (owner) { + const name = getComponentNameFromType(owner.type); if (name) { return '\n\nCheck the render method of `' + name + '`.'; } @@ -1085,11 +1076,7 @@ function validateExplicitKey(element, parentType) { // property, it may be the creator of the child that's responsible for // assigning it a key. let childOwner = ''; - if ( - element && - element._owner != null && - element._owner !== ReactSharedInternals.owner - ) { + if (element && element._owner != null && element._owner !== getOwner()) { let ownerName = null; if (typeof element._owner.tag === 'number') { ownerName = getComponentNameFromType(element._owner.type);