import type {ReactElement} from 'shared/ReactElementType';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import {
flushSyncWork,
scheduleUpdateOnFiber,
flushPendingEffects,
} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
import {updateContainerSync} from './ReactFiberReconciler';
import {emptyContextObject} from './ReactFiberLegacyContext';
import {SyncLane} from './ReactFiberLane';
import {
ClassComponent,
FunctionComponent,
ForwardRef,
MemoComponent,
SimpleMemoComponent,
} from './ReactWorkTags';
import {
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
export type Family = {
current: any,
};
export type RefreshUpdate = {
staleFamilies: Set<Family>,
updatedFamilies: Set<Family>,
};
type RefreshHandler = any => Family | void;
export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
let resolveFamily: RefreshHandler | null = null;
let failedBoundaries: WeakSet<Fiber> | null = null;
export const setRefreshHandler = (handler: RefreshHandler | null): void => {
if (__DEV__) {
resolveFamily = handler;
}
};
export function resolveTypeForHotReloading(type: any): any {
if (__DEV__) {
if (resolveFamily === null) {
return type;
}
const family = resolveFamily(type);
if (family === undefined) {
return type;
}
return family.current;
} else {
return type;
}
}
export function isCompatibleFamilyForHotReloading(
fiber: Fiber,
element: ReactElement,
): boolean {
if (__DEV__) {
if (resolveFamily === null) {
return false;
}
const resolve = resolveFamily;
const prevType = fiber.elementType;
const nextType = element.type;
let needsCompareFamilies = false;
const $$typeofNextType =
typeof nextType === 'object' && nextType !== null
? nextType.$$typeof
: null;
switch (fiber.tag) {
case ClassComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
}
break;
}
case FunctionComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
case ForwardRef: {
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
case MemoComponent:
case SimpleMemoComponent: {
if ($$typeofNextType === REACT_MEMO_TYPE) {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
default:
return false;
}
if (needsCompareFamilies) {
const prevFamily = resolve(prevType);
if (prevFamily !== undefined && prevFamily === resolve(nextType)) {
return true;
}
}
return false;
} else {
return false;
}
}
export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
if (__DEV__) {
if (resolveFamily === null) {
return;
}
if (typeof WeakSet !== 'function') {
return;
}
if (failedBoundaries === null) {
failedBoundaries = new WeakSet();
}
failedBoundaries.add(fiber);
}
}
export const scheduleRefresh: ScheduleRefresh = (
root: FiberRoot,
update: RefreshUpdate,
): void => {
if (__DEV__) {
if (resolveFamily === null) {
return;
}
const {staleFamilies, updatedFamilies} = update;
flushPendingEffects();
scheduleFibersWithFamiliesRecursively(
root.current,
updatedFamilies,
staleFamilies,
);
flushSyncWork();
}
};
export const scheduleRoot: ScheduleRoot = (
root: FiberRoot,
element: ReactNodeList,
): void => {
if (__DEV__) {
if (root.context !== emptyContextObject) {
return;
}
updateContainerSync(element, root, null, null);
flushSyncWork();
}
};
function scheduleFibersWithFamiliesRecursively(
fiber: Fiber,
updatedFamilies: Set<Family>,
staleFamilies: Set<Family>,
): void {
if (__DEV__) {
do {
const {alternate, child, sibling, tag, type, elementType} = fiber;
let candidateType = null;
let outerCandidateType = null;
switch (tag) {
case FunctionComponent:
case ClassComponent:
candidateType = type;
break;
case SimpleMemoComponent:
candidateType = type;
outerCandidateType = elementType;
break;
case MemoComponent:
outerCandidateType = elementType;
break;
case ForwardRef:
candidateType = type.render;
outerCandidateType = elementType;
break;
default:
break;
}
if (resolveFamily === null) {
throw new Error('Expected resolveFamily to be set during hot reload.');
}
const resolve = resolveFamily;
let needsRender = false;
let needsRemount = false;
if (candidateType !== null) {
const family = resolve(candidateType);
if (family !== undefined) {
if (staleFamilies.has(family)) {
needsRemount = true;
} else if (updatedFamilies.has(family)) {
if (tag === ClassComponent) {
needsRemount = true;
} else {
needsRender = true;
}
}
}
}
if (!needsRemount && outerCandidateType !== null) {
const outerFamily = resolve(outerCandidateType);
if (outerFamily !== undefined && staleFamilies.has(outerFamily)) {
needsRemount = true;
} else if (
typeof outerCandidateType === 'object' &&
outerCandidateType.$$typeof === REACT_LAZY_TYPE
) {
const payload = outerCandidateType._payload;
if (payload._status === 1 ) {
const middleFamily = resolve(payload._result.default);
if (middleFamily !== undefined && staleFamilies.has(middleFamily)) {
needsRemount = true;
}
}
}
}
if (failedBoundaries !== null) {
if (
failedBoundaries.has(fiber) ||
(alternate !== null && failedBoundaries.has(alternate))
) {
needsRemount = true;
}
}
if (needsRemount) {
fiber._debugNeedsRemount = true;
}
if (needsRemount || needsRender) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}
}
if (child !== null && !needsRemount) {
scheduleFibersWithFamiliesRecursively(
child,
updatedFamilies,
staleFamilies,
);
}
if (sibling === null) {
break;
}
fiber = sibling;
} while (true);
}
}