/*** 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,
FiberRoot,
SuspenseHydrationCallbacks,
TransitionTracingCallbacks,
} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {
Instance,
TextInstance,
Container,
PublicInstance,
RendererInspectionConfig,
} from './ReactFiberConfig';
import type {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
import type {Lane} from './ReactFiberLane';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import {
findCurrentHostFiber,
findCurrentHostFiberWithNoPortals,
} from './ReactFiberTreeReflection';
import {get as getInstance} from 'shared/ReactInstanceMap';
import {
HostComponent,
HostSingleton,
ClassComponent,
HostRoot,
SuspenseComponent,
} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import isArray from 'shared/isArray';
import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {getPublicInstance} from './ReactFiberConfig';
import {
findCurrentUnmaskedContext,
processChildContext,
emptyContextObject,
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext';
import {createFiberRoot} from './ReactFiberRoot';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {
injectInternals,
markRenderScheduled,
onScheduleRoot,
} from './ReactFiberDevToolsHook';
import {
requestUpdateLane,
scheduleUpdateOnFiber,
scheduleInitialHydrationOnRoot,
flushRoot,
batchedUpdates,
flushSync,
isAlreadyRendering,
deferredUpdates,
discreteUpdates,
flushPassiveEffects,
} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
import {
createUpdate,
enqueueUpdate,
entangleTransitions,
} from './ReactFiberClassUpdateQueue';
import {
isRendering as ReactCurrentFiberIsRendering,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {
SyncLane,
SelectiveHydrationLane,
getHighestPriorityPendingLanes,
higherPriorityLane,
} from './ReactFiberLane';
import {
getCurrentUpdatePriority,
runWithPriority,
} from './ReactEventPriorities';
import {
scheduleRefresh,
scheduleRoot,
setRefreshHandler,
findHostInstancesForRefresh,
} from './ReactFiberHotReloading';
import ReactVersion from 'shared/ReactVersion';
export {createPortal} from './ReactPortal';
export {
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
createTestNameSelector,
createTextSelector,
getFindAllNodesFailureDescription,
findAllNodes,
findBoundingRects,
focusWithin,
observeVisibleRects,
} from './ReactTestSelectors';
export {startHostTransition} from './ReactFiberHooks';
type OpaqueRoot = FiberRoot;
// 0 is PROD, 1 is DEV.// Might add PROFILE later.type BundleType = 0 | 1;
type DevToolsConfig = {
bundleType: BundleType,
version: string,
rendererPackageName: string,
// Note: this actually *does* depend on Fiber internal fields.
// Used by "inspect clicked DOM element" in React DevTools.
findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
rendererConfig?: RendererInspectionConfig,
};let didWarnAboutNestedUpdates;
let didWarnAboutFindNodeInStrictMode;
if (__DEV__) {
didWarnAboutNestedUpdates = false;
didWarnAboutFindNodeInStrictMode = ({}: {[string]: boolean});
}function getContextForSubtree(
parentComponent: ?React$Component<any, any>,): Object {
if (!parentComponent) {
return emptyContextObject;
}const fiber = getInstance(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
if (fiber.tag === ClassComponent) {
const Component = fiber.type;
if (isLegacyContextProvider(Component)) {
return processChildContext(fiber, Component, parentContext);
}}return parentContext;
}function findHostInstance(component: Object): PublicInstance | null {
const fiber = getInstance(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
throw new Error('Unable to find node on an unmounted component.');
} else {
const keys = Object.keys(component).join(',');
throw new Error(
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
);}}const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}return getPublicInstance(hostFiber.stateNode);
}function findHostInstanceWithWarning(
component: Object,methodName: string,): PublicInstance | null {
if (__DEV__) {
const fiber = getInstance(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
throw new Error('Unable to find node on an unmounted component.');
} else {
const keys = Object.keys(component).join(',');
throw new Error(
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
);}}const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}if (hostFiber.mode & StrictLegacyMode) {
const componentName = getComponentNameFromFiber(fiber) || 'Component';
if (!didWarnAboutFindNodeInStrictMode[componentName]) {
didWarnAboutFindNodeInStrictMode[componentName] = true;
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(hostFiber);
if (fiber.mode & StrictLegacyMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-find-node',
methodName,
methodName,
componentName,
);} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-find-node',
methodName,
methodName,
componentName,
);}} finally {
// Ideally this should reset to previous but this shouldn't be called in
// render and there's another warning for that anyway.
if (previousFiber) {
setCurrentDebugFiberInDEV(previousFiber);
} else {
resetCurrentDebugFiberInDEV();
}}}}return getPublicInstance(hostFiber.stateNode);
}return findHostInstance(component);
}export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
null,
);}export function createHydrationContainer(
initialChildren: ReactNodeList,
// TODO: Remove `callback` when we delete legacy mode.
callback: ?Function,
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
formState: ReactFormState<any, any> | null,
): OpaqueRoot {
const hydrate = true;
const root = createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
formState,
);// TODO: Move this to FiberRoot constructor
root.context = getContextForSubtree(null);
// Schedule the initial render. In a hydration root, this is different from
// a regular update because the initial render must match was was rendered
// on the server.
// NOTE: This update intentionally doesn't have a payload. We're only using
// the update to schedule work on the root fiber (and, for legacy roots, to
// enqueue the callback if one is provided).
const current = root.current;
const lane = requestUpdateLane(current);
const update = createUpdate(lane);
update.callback =
callback !== undefined && callback !== null ? callback : null;
enqueueUpdate(current, update, lane);
scheduleInitialHydrationOnRoot(root, lane);
return root;
}export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
if (__DEV__) {
onScheduleRoot(container, element);
}const current = container.current;
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}if (__DEV__) {
if (
ReactCurrentFiberIsRendering &&
ReactCurrentFiberCurrent !== null &&
!didWarnAboutNestedUpdates
) {didWarnAboutNestedUpdates = true;
console.error(
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
);}}const update = createUpdate(lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
if (__DEV__) {
if (typeof callback !== 'function') {
console.error(
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);}}update.callback = callback;
}const root = enqueueUpdate(current, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, current, lane);
entangleTransitions(root, current, lane);
}return lane;
}export {
batchedUpdates,
deferredUpdates,
discreteUpdates,
flushSync,
isAlreadyRendering,
flushPassiveEffects,
};export function getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}switch (containerFiber.child.tag) {
case HostSingleton:
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}}export function attemptSynchronousHydration(fiber: Fiber): void {
switch (fiber.tag) {
case HostRoot: {
const root: FiberRoot = fiber.stateNode;
if (isRootDehydrated(root)) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
flushRoot(root, lanes);
}break;
}case SuspenseComponent: {
flushSync(() => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}});// If we're still blocked after this, we need to increase
// the priority of any promises resolving within this
// boundary so that they next attempt also has higher pri.
const retryLane = SyncLane;
markRetryLaneIfNotHydrated(fiber, retryLane);
break;
}}}function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
const suspenseState: null | SuspenseState = fiber.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
suspenseState.retryLane = higherPriorityLane(
suspenseState.retryLane,
retryLane,
);}}// Increases the priority of thenables when they resolve within this boundary.function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
markRetryLaneImpl(fiber, retryLane);
const alternate = fiber.alternate;
if (alternate) {
markRetryLaneImpl(alternate, retryLane);
}}export function attemptContinuousHydration(fiber: Fiber): void {
if (fiber.tag !== SuspenseComponent) {
// We ignore HostRoots here because we can't increase
// their priority and they should not suspend on I/O,
// since you have to wrap anything that might suspend in
// Suspense.
return;
}const lane = SelectiveHydrationLane;const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
}markRetryLaneIfNotHydrated(fiber, lane);
}export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
if (fiber.tag !== SuspenseComponent) {
// We ignore HostRoots here because we can't increase
// their priority other than synchronously flush it.
return;
}const lane = requestUpdateLane(fiber);
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
}markRetryLaneIfNotHydrated(fiber, lane);
}export {getCurrentUpdatePriority, runWithPriority};
export {findHostInstance};
export {findHostInstanceWithWarning};
export function findHostInstanceWithNoPortals(
fiber: Fiber,
): PublicInstance | null {
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
if (hostFiber === null) {
return null;
}return getPublicInstance(hostFiber.stateNode);
}let shouldErrorImpl: Fiber => ?boolean = fiber => null;
export function shouldError(fiber: Fiber): ?boolean {
return shouldErrorImpl(fiber);
}let shouldSuspendImpl = (fiber: Fiber) => false;
export function shouldSuspend(fiber: Fiber): boolean {
return shouldSuspendImpl(fiber);
}let overrideHookState = null;
let overrideHookStateDeletePath = null;
let overrideHookStateRenamePath = null;
let overrideProps = null;
let overridePropsDeletePath = null;
let overridePropsRenamePath = null;
let scheduleUpdate = null;
let setErrorHandler = null;
let setSuspenseHandler = null;
if (__DEV__) {
const copyWithDeleteImpl = (
obj: Object | Array<any>,
path: Array<string | number>,
index: number,
): $FlowFixMe => {
const key = path[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
if (index + 1 === path.length) {
if (isArray(updated)) {
updated.splice(((key: any): number), 1);
} else {
delete updated[key];
}return updated;
}// $FlowFixMe[incompatible-use] number or string is fine here
updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
return updated;
};const copyWithDelete = (
obj: Object | Array<any>,
path: Array<string | number>,
): Object | Array<any> => {
return copyWithDeleteImpl(obj, path, 0);
};const copyWithRenameImpl = (
obj: Object | Array<any>,
oldPath: Array<string | number>,
newPath: Array<string | number>,
index: number,
): $FlowFixMe => {
const oldKey = oldPath[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
if (index + 1 === oldPath.length) {
const newKey = newPath[index];
// $FlowFixMe[incompatible-use] number or string is fine here
updated[newKey] = updated[oldKey];
if (isArray(updated)) {
updated.splice(((oldKey: any): number), 1);
} else {
delete updated[oldKey];
}} else {
// $FlowFixMe[incompatible-use] number or string is fine here
updated[oldKey] = copyWithRenameImpl(
// $FlowFixMe[incompatible-use] number or string is fine here
obj[oldKey],
oldPath,
newPath,
index + 1,
);}return updated;
};const copyWithRename = (
obj: Object | Array<any>,
oldPath: Array<string | number>,
newPath: Array<string | number>,
): Object | Array<any> => {
if (oldPath.length !== newPath.length) {
console.warn('copyWithRename() expects paths of the same length');
return;
} else {for (let i = 0; i < newPath.length - 1; i++) {
if (oldPath[i] !== newPath[i]) {
console.warn(
'copyWithRename() expects paths to be the same except for the deepest key',
);return;
}}}return copyWithRenameImpl(obj, oldPath, newPath, 0);
};const copyWithSetImpl = (
obj: Object | Array<any>,
path: Array<string | number>,
index: number,
value: any,
): $FlowFixMe => {
if (index >= path.length) {
return value;
}const key = path[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
// $FlowFixMe[incompatible-use] number or string is fine here
updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
return updated;
};const copyWithSet = (
obj: Object | Array<any>,
path: Array<string | number>,
value: any,
): Object | Array<any> => {
return copyWithSetImpl(obj, path, 0, value);
};const findHook = (fiber: Fiber, id: number) => {
// For now, the "id" of stateful hooks is just the stateful hook index.
// This may change in the future with e.g. nested hooks.
let currentHook = fiber.memoizedState;
while (currentHook !== null && id > 0) {
currentHook = currentHook.next;
id--;
}return currentHook;
};// Support DevTools editable values for useState and useReducer.
overrideHookState = (
fiber: Fiber,
id: number,
path: Array<string | number>,
value: any,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithSet(hook.memoizedState, path, value);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}}};overrideHookStateDeletePath = (
fiber: Fiber,
id: number,
path: Array<string | number>,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithDelete(hook.memoizedState, path);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}}};overrideHookStateRenamePath = (
fiber: Fiber,
id: number,
oldPath: Array<string | number>,
newPath: Array<string | number>,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithRename(hook.memoizedState, oldPath, newPath);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}}};// Support DevTools props for function components, forwardRef, memo, host components, etc.
overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}};overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}};overridePropsRenamePath = (
fiber: Fiber,
oldPath: Array<string | number>,
newPath: Array<string | number>,
) => {
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}};scheduleUpdate = (fiber: Fiber) => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}};setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
shouldErrorImpl = newShouldErrorImpl;
};setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
shouldSuspendImpl = newShouldSuspendImpl;
};}function findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}return hostFiber.stateNode;}function emptyFindFiberByHostInstance(
instance: Instance | TextInstance,): Fiber | null {
return null;}function getCurrentFiberForDevTools() {
return ReactCurrentFiberCurrent;
}export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
const {findFiberByHostInstance} = devToolsConfig;
const {ReactCurrentDispatcher} = ReactSharedInternals;
return injectInternals({
bundleType: devToolsConfig.bundleType,
version: devToolsConfig.version,
rendererPackageName: devToolsConfig.rendererPackageName,
rendererConfig: devToolsConfig.rendererConfig,
overrideHookState,
overrideHookStateDeletePath,
overrideHookStateRenamePath,
overrideProps,
overridePropsDeletePath,
overridePropsRenamePath,
setErrorHandler,
setSuspenseHandler,
scheduleUpdate,
currentDispatcherRef: ReactCurrentDispatcher,
findHostInstanceByFiber,
findFiberByHostInstance:findFiberByHostInstance || emptyFindFiberByHostInstance,
// React Refresh
findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
scheduleRefresh: __DEV__ ? scheduleRefresh : null,
scheduleRoot: __DEV__ ? scheduleRoot : null,
setRefreshHandler: __DEV__ ? setRefreshHandler : null,
// Enables DevTools to append owner stacks to error messages in DEV mode.
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
// Enables DevTools to detect reconciler version rather than renderer version
// which may not match for third party renderers.
reconcilerVersion: ReactVersion,
});}