Skip to content

Instantly share code, notes, and snippets.

@gaearon
Created June 19, 2019 13:19
Show Gist options
  • Save gaearon/ddbda5845b4dba0d9915e2ed7f7b11e2 to your computer and use it in GitHub Desktop.
Save gaearon/ddbda5845b4dba0d9915e2ed7f7b11e2 to your computer and use it in GitHub Desktop.
--- a/packages/react-reconciler/src/ReactChildFiber.js
+++ b/packages/react-reconciler/src/ReactChildFiber.js
@@ -43,6 +43,7 @@ import {
getCurrentFiberStackInDev,
getStackByFiberInDevAndProd,
} from './ReactCurrentFiber';
+import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
import {StrictMode} from './ReactTypeOfMode';
let didWarnAboutMaps;
@@ -378,7 +379,12 @@ function ChildReconciler(shouldTrackSideEffects) {
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
- if (current !== null && current.elementType === element.type) {
+ if (
+ current !== null &&
+ (current.elementType === element.type ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false))
+ ) {
// Move based on index
const existing = useFiber(current, element.props, expirationTime);
existing.ref = coerceRef(returnFiber, current, element);
@@ -1121,7 +1127,11 @@ function ChildReconciler(shouldTrackSideEffects) {
if (
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
- : child.elementType === element.type
+ : child.elementType === element.type ||
+ // Keep this check inline so it only runs on the false path:
+ (__DEV__
+ ? isCompatibleFamilyForHotReloading(child, element)
+ : false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(
diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js
index 8ca24a16a..4996971d1 100644
--- a/packages/react-reconciler/src/ReactFiber.js
+++ b/packages/react-reconciler/src/ReactFiber.js
@@ -43,6 +43,7 @@ import {
SuspenseComponent,
FunctionComponent,
MemoComponent,
+ SimpleMemoComponent,
LazyComponent,
EventComponent,
EventTarget,
@@ -50,6 +51,11 @@ import {
import getComponentName from 'shared/getComponentName';
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
+import {
+ resolveClassForHotReloading,
+ resolveFunctionForHotReloading,
+ resolveForwardRefForHotReloading,
+} from './ReactFiberHotReloading';
import {NoWork} from './ReactFiberExpirationTime';
import {
NoContext,
@@ -215,6 +221,7 @@ export type Fiber = {|
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
+ _debugNeedsRemount?: boolean,
// Used to verify that the order of hooks does not change between renders.
_debugHookTypes?: Array<HookType> | null,
@@ -299,6 +306,7 @@ function FiberNode(
this._debugSource = null;
this._debugOwner = null;
this._debugIsCurrentlyTiming = false;
+ this._debugNeedsRemount = false;
this._debugHookTypes = null;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
@@ -431,6 +439,25 @@ export function createWorkInProgress(
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
+ if (__DEV__) {
+ workInProgress._debugNeedsRemount = current._debugNeedsRemount;
+ switch (workInProgress.tag) {
+ case IndeterminateComponent:
+ case FunctionComponent:
+ case SimpleMemoComponent:
+ workInProgress.type = resolveFunctionForHotReloading(current.type);
+ break;
+ case ClassComponent:
+ workInProgress.type = resolveClassForHotReloading(current.type);
+ break;
+ case ForwardRef:
+ workInProgress.type = resolveForwardRefForHotReloading(current.type);
+ break;
+ default:
+ break;
+ }
+ }
+
return workInProgress;
}
@@ -463,6 +490,13 @@ export function createFiberFromTypeAndProps(
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
+ if (__DEV__) {
+ resolvedType = resolveClassForHotReloading(resolvedType);
+ }
+ } else {
+ if (__DEV__) {
+ resolvedType = resolveFunctionForHotReloading(resolvedType);
+ }
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
@@ -505,6 +539,9 @@ export function createFiberFromTypeAndProps(
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
+ if (__DEV__) {
+ resolvedType = resolveForwardRefForHotReloading(resolvedType);
+ }
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
@@ -793,6 +830,7 @@ export function assignFiberPropertiesInDEV(
target._debugSource = source._debugSource;
target._debugOwner = source._debugOwner;
target._debugIsCurrentlyTiming = source._debugIsCurrentlyTiming;
+ target._debugNeedsRemount = source._debugNeedsRemount;
target._debugHookTypes = source._debugHookTypes;
return target;
}
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 8659e010a..6e854aa75 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -70,6 +70,11 @@ import {
getCurrentFiberStackInDev,
} from './ReactCurrentFiber';
import {startWorkTimer, cancelWorkTimer} from './ReactDebugFiberPerf';
+import {
+ resolveFunctionForHotReloading,
+ resolveForwardRefForHotReloading,
+ resolveClassForHotReloading,
+} from './ReactFiberHotReloading';
import {
mountChildFibers,
@@ -346,18 +351,22 @@ function updateMemoComponent(
// SimpleMemoComponent codepath doesn't resolve outer props either.
Component.defaultProps === undefined
) {
+ let resolvedType = type;
+ if (__DEV__) {
+ resolvedType = resolveFunctionForHotReloading(type);
+ }
// If this is a plain function component without default props,
// and with only the default shallow comparison, we upgrade it
// to a SimpleMemoComponent to allow fast path updates.
workInProgress.tag = SimpleMemoComponent;
- workInProgress.type = type;
+ workInProgress.type = resolvedType;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, type);
}
return updateSimpleMemoComponent(
current,
workInProgress,
- type,
+ resolvedType,
nextProps,
updateExpirationTime,
renderExpirationTime,
@@ -474,7 +483,9 @@ function updateSimpleMemoComponent(
const prevProps = current.memoizedProps;
if (
shallowEqual(prevProps, nextProps) &&
- current.ref === workInProgress.ref
+ current.ref === workInProgress.ref &&
+ // Prevent bailout if the implementation changed due to hot reload:
+ (__DEV__ ? workInProgress.type === current.type : true)
) {
didReceiveUpdate = false;
if (updateExpirationTime < renderExpirationTime) {
@@ -1021,6 +1032,9 @@ function mountLazyComponent(
case FunctionComponent: {
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
+ workInProgress.type = Component = resolveFunctionForHotReloading(
+ Component,
+ );
}
child = updateFunctionComponent(
null,
@@ -1032,6 +1046,11 @@ function mountLazyComponent(
break;
}
case ClassComponent: {
+ if (__DEV__) {
+ workInProgress.type = Component = resolveClassForHotReloading(
+ Component,
+ );
+ }
child = updateClassComponent(
null,
workInProgress,
@@ -1042,6 +1061,11 @@ function mountLazyComponent(
break;
}
case ForwardRef: {
+ if (__DEV__) {
+ workInProgress.type = Component = resolveForwardRefForHotReloading(
+ Component,
+ );
+ }
child = updateForwardRef(
null,
workInProgress,
@@ -2057,6 +2081,68 @@ function bailoutOnAlreadyFinishedWork(
}
}
+function remountFiber(
+ current: Fiber,
+ oldWorkInProgress: Fiber,
+ newWorkInProgress: Fiber,
+): Fiber | null {
+ if (__DEV__) {
+ const returnFiber = oldWorkInProgress.return;
+ if (returnFiber === null) {
+ throw new Error('Cannot swap the root fiber.');
+ }
+
+ // Disconnect from the old current.
+ // It will get deleted.
+ current.alternate = null;
+ oldWorkInProgress.alternate = null;
+
+ // Connect to the new tree.
+ newWorkInProgress.index = oldWorkInProgress.index;
+ newWorkInProgress.sibling = oldWorkInProgress.sibling;
+ newWorkInProgress.return = oldWorkInProgress.return;
+
+ // Replace the child/sibling pointers above it.
+ if (oldWorkInProgress === returnFiber.child) {
+ returnFiber.child = newWorkInProgress;
+ } else {
+ let prevSibling = returnFiber.child;
+ if (prevSibling === null) {
+ throw new Error('Expected parent to have a child.');
+ }
+ while (prevSibling.sibling !== oldWorkInProgress) {
+ prevSibling = prevSibling.sibling;
+ if (prevSibling === null) {
+ throw new Error('Expected to find the previous sibling.');
+ }
+ }
+ prevSibling.sibling = newWorkInProgress;
+ }
+
+ // Delete the old fiber and place the new one.
+ // Since the old fiber is disconnected, we have to schedule it manually.
+ const last = returnFiber.lastEffect;
+ if (last !== null) {
+ last.nextEffect = current;
+ returnFiber.lastEffect = current;
+ } else {
+ returnFiber.firstEffect = returnFiber.lastEffect = current;
+ }
+ current.nextEffect = null;
+ current.effectTag = Deletion;
+
+ newWorkInProgress.effectTag |= Placement;
+
+ // Restart work from the new fiber.
+ return newWorkInProgress;
+ } else {
+ throw new Error(
+ 'Did not expect this call in production. ' +
+ 'This is a bug in React. Please file an issue.',
+ );
+ }
+}
+
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
@@ -2064,11 +2150,34 @@ function beginWork(
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
+ if (__DEV__) {
+ if (workInProgress._debugNeedsRemount && current !== null) {
+ // This will restart the begin phase with a new fiber.
+ return remountFiber(
+ current,
+ workInProgress,
+ createFiberFromTypeAndProps(
+ workInProgress.type,
+ workInProgress.key,
+ workInProgress.pendingProps,
+ workInProgress._debugOwner || null,
+ workInProgress.mode,
+ workInProgress.expirationTime,
+ ),
+ );
+ }
+ }
+
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
- if (oldProps !== newProps || hasLegacyContextChanged()) {
+ if (
+ oldProps !== newProps ||
+ hasLegacyContextChanged() ||
+ // Force a re-render if the implementation changed due to hot reload:
+ (__DEV__ ? workInProgress.type !== current.type : false)
+ ) {
// 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;
@@ -2185,11 +2294,10 @@ function beginWork(
switch (workInProgress.tag) {
case IndeterminateComponent: {
- const elementType = workInProgress.elementType;
return mountIndeterminateComponent(
current,
workInProgress,
- elementType,
+ workInProgress.type,
renderExpirationTime,
);
}
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index b5b7f9dd7..c14cf9649 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -185,6 +185,11 @@ let currentHookNameInDev: ?HookType = null;
let hookTypesDev: Array<HookType> | null = null;
let hookTypesUpdateIndexDev: number = -1;
+// In DEV, this tracks whether currently rendering component needs to ignore
+// the dependencies for Hooks that need them (e.g. useEffect or useMemo).
+// When true, such Hooks will always be "remounted". Only used during hot reload.
+let ignorePreviousDependencies: boolean = false;
+
function mountHookTypesDev() {
if (__DEV__) {
const hookName = ((currentHookNameInDev: any): HookType);
@@ -292,6 +297,13 @@ function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
+ if (__DEV__) {
+ if (ignorePreviousDependencies) {
+ // Only true when this component is being hot reloaded.
+ return false;
+ }
+ }
+
if (prevDeps === null) {
if (__DEV__) {
warning(
@@ -348,6 +360,9 @@ export function renderWithHooks(
? ((current._debugHookTypes: any): Array<HookType>)
: null;
hookTypesUpdateIndexDev = -1;
+ // Used for hot reloading:
+ ignorePreviousDependencies =
+ current !== null && current.type !== workInProgress.type;
}
// The following should have already been reset
diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.js b/packages/react-reconciler/src/ReactFiberHotReloading.js
new file mode 100644
index 000000000..dc1960c72
--- /dev/null
+++ b/packages/react-reconciler/src/ReactFiberHotReloading.js
@@ -0,0 +1,436 @@
+/**
+ * Copyright (c) Facebook, Inc. and its 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 {ReactElement} from 'shared/ReactElementType';
+import type {Fiber} from './ReactFiber';
+import type {FiberRoot} from './ReactFiberRoot';
+import type {Instance} from './ReactFiberHostConfig';
+
+import {
+ flushSync,
+ scheduleWork,
+ flushPassiveEffects,
+} from './ReactFiberScheduler';
+import {Sync} from './ReactFiberExpirationTime';
+import {
+ ClassComponent,
+ FunctionComponent,
+ ForwardRef,
+ HostComponent,
+ HostPortal,
+ HostRoot,
+ MemoComponent,
+ SimpleMemoComponent,
+} from 'shared/ReactWorkTags';
+import {
+ REACT_FORWARD_REF_TYPE,
+ REACT_MEMO_TYPE,
+ REACT_LAZY_TYPE,
+} from 'shared/ReactSymbols';
+
+export type Family = {|
+ current: any,
+|};
+
+export type HotUpdate = {|
+ resolveFamily: (any => Family | void) | null,
+ staleFamilies: Set<Family>,
+ updatedFamilies: Set<Family>,
+|};
+
+let resolveFamily: (any => Family | void) | null = null;
+// $FlowFixMe Flow gets confused by a WeakSet feature check below.
+let failedBoundaries: WeakSet<Fiber> | null = null;
+
+export function resolveFunctionForHotReloading(type: any): any {
+ if (__DEV__) {
+ if (resolveFamily === null) {
+ // Hot reloading is disabled.
+ return type;
+ }
+ let family = resolveFamily(type);
+ if (family === undefined) {
+ return type;
+ }
+ // Use the latest known implementation.
+ return family.current;
+ } else {
+ return type;
+ }
+}
+
+export function resolveClassForHotReloading(type: any): any {
+ // No implementation differences.
+ return resolveFunctionForHotReloading(type);
+}
+
+export function resolveForwardRefForHotReloading(type: any): any {
+ if (__DEV__) {
+ if (resolveFamily === null) {
+ // Hot reloading is disabled.
+ return type;
+ }
+ let family = resolveFamily(type);
+ if (family === undefined) {
+ // Check if we're dealing with a real forwardRef. Don't want to crash early.
+ if (
+ type !== null &&
+ type !== undefined &&
+ typeof type.render === 'function'
+ ) {
+ // ForwardRef is special because its resolved .type is an object,
+ // but it's possible that we only have its inner render function in the map.
+ // If that inner render function is different, we'll build a new forwardRef type.
+ const currentRender = resolveFunctionForHotReloading(type.render);
+ if (type.render !== currentRender) {
+ const syntheticType = {
+ $$typeof: REACT_FORWARD_REF_TYPE,
+ render: currentRender,
+ };
+ if (type.displayName !== undefined) {
+ (syntheticType: any).displayName = type.displayName;
+ }
+ return syntheticType;
+ }
+ }
+ return type;
+ }
+ // Use the latest known implementation.
+ return family.current;
+ } else {
+ return type;
+ }
+}
+
+export function isCompatibleFamilyForHotReloading(
+ fiber: Fiber,
+ element: ReactElement,
+): boolean {
+ if (__DEV__) {
+ if (resolveFamily === null) {
+ // Hot reloading is disabled.
+ return false;
+ }
+
+ const prevType = fiber.elementType;
+ const nextType = element.type;
+
+ // If we got here, we know types aren't === equal.
+ 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) {
+ // We don't know the inner type yet.
+ // We're going to assume that the lazy inner type is stable,
+ // and so it is sufficient to avoid reconciling it away.
+ // We're not going to unwrap or actually use the new 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) {
+ // TODO: if it was but can no longer be simple,
+ // we shouldn't set this.
+ needsCompareFamilies = true;
+ } else if ($$typeofNextType === REACT_LAZY_TYPE) {
+ needsCompareFamilies = true;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+
+ // Check if both types have a family and it's the same one.
+ if (needsCompareFamilies) {
+ // Note: memo() and forwardRef() we'll compare outer rather than inner type.
+ // This means both of them need to be registered to preserve state.
+ // If we unwrapped and compared the inner types for wrappers instead,
+ // then we would risk falsely saying two separate memo(Foo)
+ // calls are equivalent because they wrap the same Foo function.
+ const prevFamily = resolveFamily(prevType);
+ if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return false;
+ }
+}
+
+export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
+ if (__DEV__) {
+ if (resolveFamily === null) {
+ // Not hot reloading.
+ return;
+ }
+ if (typeof WeakSet !== 'function') {
+ return;
+ }
+ if (failedBoundaries === null) {
+ failedBoundaries = new WeakSet();
+ }
+ failedBoundaries.add(fiber);
+ }
+}
+
+export function scheduleHotUpdate(root: FiberRoot, hotUpdate: HotUpdate): void {
+ if (__DEV__) {
+ // TODO: warn if its identity changes over time?
+ resolveFamily = hotUpdate.resolveFamily;
+
+ const {staleFamilies, updatedFamilies} = hotUpdate;
+ flushPassiveEffects();
+ flushSync(() => {
+ scheduleFibersWithFamiliesRecursively(
+ root.current,
+ updatedFamilies,
+ staleFamilies,
+ );
+ });
+ }
+}
+
+function scheduleFibersWithFamiliesRecursively(
+ fiber: Fiber,
+ updatedFamilies: Set<Family>,
+ staleFamilies: Set<Family>,
+) {
+ if (__DEV__) {
+ const {alternate, child, sibling, tag, type} = fiber;
+
+ let candidateType = null;
+ switch (tag) {
+ case FunctionComponent:
+ case SimpleMemoComponent:
+ case ClassComponent:
+ candidateType = type;
+ break;
+ case ForwardRef:
+ candidateType = type.render;
+ break;
+ default:
+ break;
+ }
+
+ if (resolveFamily === null) {
+ throw new Error('Expected resolveFamily to be set during hot reload.');
+ }
+
+ let needsRender = false;
+ let needsRemount = false;
+ if (candidateType !== null) {
+ const family = resolveFamily(candidateType);
+ if (family !== undefined) {
+ if (staleFamilies.has(family)) {
+ needsRemount = true;
+ } else if (updatedFamilies.has(family)) {
+ needsRender = true;
+ }
+ }
+ }
+ if (failedBoundaries !== null) {
+ if (
+ failedBoundaries.has(fiber) ||
+ (alternate !== null && failedBoundaries.has(alternate))
+ ) {
+ needsRemount = true;
+ }
+ }
+
+ if (needsRemount) {
+ fiber._debugNeedsRemount = true;
+ }
+ if (needsRemount || needsRender) {
+ scheduleWork(fiber, Sync);
+ }
+ if (child !== null && !needsRemount) {
+ scheduleFibersWithFamiliesRecursively(
+ child,
+ updatedFamilies,
+ staleFamilies,
+ );
+ }
+ if (sibling !== null) {
+ scheduleFibersWithFamiliesRecursively(
+ sibling,
+ updatedFamilies,
+ staleFamilies,
+ );
+ }
+ }
+}
+
+export function findHostInstancesForHotUpdate(
+ root: FiberRoot,
+ families: Array<Family>,
+): Set<Instance> {
+ if (__DEV__) {
+ const hostInstances = new Set();
+ const types = new Set(families.map(family => family.current));
+ findHostInstancesForMatchingFibersRecursively(
+ root.current,
+ types,
+ hostInstances,
+ );
+ return hostInstances;
+ } else {
+ throw new Error(
+ 'Did not expect findHostInstancesForHotUpdate to be called in production.',
+ );
+ }
+}
+
+function findHostInstancesForMatchingFibersRecursively(
+ fiber: Fiber,
+ types: Set<any>,
+ hostInstances: Set<Instance>,
+) {
+ if (__DEV__) {
+ const {child, sibling, tag, type} = fiber;
+
+ let candidateType = null;
+ switch (tag) {
+ case FunctionComponent:
+ case SimpleMemoComponent:
+ case ClassComponent:
+ candidateType = type;
+ break;
+ case ForwardRef:
+ candidateType = type.render;
+ break;
+ default:
+ break;
+ }
+
+ let didMatch = false;
+ if (candidateType !== null) {
+ if (types.has(candidateType)) {
+ didMatch = true;
+ }
+ }
+
+ if (didMatch) {
+ // We have a match. This only drills down to the closest host components.
+ // There's no need to search deeper because for the purpose of giving
+ // visual feedback, "flashing" outermost parent rectangles is sufficient.
+ findHostInstancesForFiberShallowly(fiber, hostInstances);
+ } else {
+ // If there's no match, maybe there will be one further down in the child tree.
+ if (child !== null) {
+ findHostInstancesForMatchingFibersRecursively(
+ child,
+ types,
+ hostInstances,
+ );
+ }
+ }
+
+ if (sibling !== null) {
+ findHostInstancesForMatchingFibersRecursively(
+ sibling,
+ types,
+ hostInstances,
+ );
+ }
+ }
+}
+
+function findHostInstancesForFiberShallowly(
+ fiber: Fiber,
+ hostInstances: Set<Instance>,
+): void {
+ if (__DEV__) {
+ const foundHostInstances = findChildHostInstancesForFiberShallowly(
+ fiber,
+ hostInstances,
+ );
+ if (foundHostInstances) {
+ return;
+ }
+ // If we didn't find any host children, fallback to closest host parent.
+ let node = fiber;
+ while (true) {
+ switch (node.tag) {
+ case HostComponent:
+ hostInstances.add(node.stateNode);
+ return;
+ case HostPortal:
+ hostInstances.add(node.stateNode.containerInfo);
+ return;
+ case HostRoot:
+ hostInstances.add(node.stateNode.containerInfo);
+ return;
+ }
+ if (node.return === null) {
+ throw new Error('Expected to reach root first.');
+ }
+ node = node.return;
+ }
+ }
+}
+
+function findChildHostInstancesForFiberShallowly(
+ fiber: Fiber,
+ hostInstances: Set<Instance>,
+): boolean {
+ if (__DEV__) {
+ let node: Fiber = fiber;
+ let foundHostInstances = false;
+ while (true) {
+ if (node.tag === HostComponent) {
+ // We got a match.
+ foundHostInstances = true;
+ hostInstances.add(node.stateNode);
+ // There may still be more, so keep searching.
+ } else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
+ if (node === fiber) {
+ return foundHostInstances;
+ }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === fiber) {
+ return foundHostInstances;
+ }
+ node = node.return;
+ }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+ }
+ return false;
+}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index 77fadd31e..10f7b7662 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -63,6 +63,10 @@ import {
} from './ReactCurrentFiber';
import {StrictMode} from './ReactTypeOfMode';
import {Sync} from './ReactFiberExpirationTime';
+import {
+ scheduleHotUpdate,
+ findHostInstancesForHotUpdate,
+} from './ReactFiberHotReloading';
type OpaqueRoot = FiberRoot;
@@ -434,6 +438,10 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
return injectInternals({
...devToolsConfig,
+ findHostInstancesForHotUpdate: __DEV__
+ ? findHostInstancesForHotUpdate
+ : null,
+ scheduleHotUpdate: __DEV__ ? scheduleHotUpdate : null,
overrideHookState,
overrideProps,
setSuspenseHandler,
diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js
index c6c827673..f2ccc813c 100644
--- a/packages/react-reconciler/src/ReactFiberUnwindWork.js
+++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js
@@ -55,6 +55,7 @@ import {
import {logError} from './ReactFiberCommitWork';
import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
+import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading';
import {
isContextProvider as isLegacyContextProvider,
popContext as popLegacyContext,
@@ -114,6 +115,9 @@ function createClassErrorUpdate(
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
+ if (__DEV__) {
+ markFailedErrorBoundaryForHotReloading(fiber);
+ }
if (typeof getDerivedStateFromError !== 'function') {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
@@ -142,6 +146,10 @@ function createClassErrorUpdate(
}
}
};
+ } else if (__DEV__) {
+ update.callback = () => {
+ markFailedErrorBoundaryForHotReloading(fiber);
+ };
}
return update;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment