diff --git a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
new file mode 100644
index 0000000000000..688fb5c926ed5
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
@@ -0,0 +1,61 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ */
+
+let React;
+let ReactNoop;
+
+beforeEach(() => {
+ React = require('react');
+ ReactNoop = require('react-noop-renderer');
+});
+
+// Don't feel too guilty if you have to delete this test.
+// @gate new
+// @gate __DEV__
+test('warns in DEV if return pointer is inconsistent', async () => {
+ const {useRef, useLayoutEffect} = React;
+
+ let ref = null;
+ function App({text}) {
+ ref = useRef(null);
+ return (
+ <>
+
+
{text}
+ >
+ );
+ }
+
+ function Sibling({text}) {
+ useLayoutEffect(() => {
+ if (text === 'B') {
+ // Mutate the return pointer of the div to point to the wrong alternate.
+ // This simulates the most common type of return pointer inconsistency.
+ const current = ref.current.fiber;
+ const workInProgress = current.alternate;
+ workInProgress.return = current.return;
+ }
+ }, [text]);
+ return null;
+ }
+
+ const root = ReactNoop.createRoot();
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+
+ spyOnDev(console, 'error');
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(console.error.calls.count()).toBe(1);
+ expect(console.error.calls.argsFor(0)[0]).toMatch(
+ 'Internal React error: Return pointer is inconsistent with parent.',
+ );
+});
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 386d040e90757..ec2a6d714b4f9 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -275,6 +275,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
+ internalInstanceHandle: Object,
): Instance {
if (type === 'errorInCompletePhase') {
throw new Error('Error in host config.');
@@ -300,6 +301,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
value: inst.context,
enumerable: false,
});
+ Object.defineProperty(inst, 'fiber', {
+ value: internalInstanceHandle,
+ enumerable: false,
+ });
return inst;
},
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
index 55c7c986effc7..e45db46500faf 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
@@ -463,6 +463,7 @@ function iterativelyCommitBeforeMutationEffects_begin() {
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
+ warnIfWrongReturnPointer(fiber, child);
nextEffect = child;
} else {
iterativelyCommitBeforeMutationEffects_complete();
@@ -496,6 +497,7 @@ function iterativelyCommitBeforeMutationEffects_complete() {
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -713,6 +715,7 @@ function iterativelyCommitMutationEffects_begin(
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
+ warnIfWrongReturnPointer(fiber, child);
nextEffect = child;
} else {
iterativelyCommitMutationEffects_complete(root, renderPriorityLevel);
@@ -751,6 +754,7 @@ function iterativelyCommitMutationEffects_complete(
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -1172,12 +1176,14 @@ function iterativelyCommitLayoutEffects_begin(
}
const sibling = finishedWork.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(finishedWork.return, sibling);
nextEffect = sibling;
} else {
nextEffect = finishedWork.return;
iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot);
}
} else {
+ warnIfWrongReturnPointer(finishedWork, firstChild);
nextEffect = firstChild;
}
} else {
@@ -1224,6 +1230,7 @@ function iterativelyCommitLayoutEffects_complete(
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -1757,12 +1764,14 @@ function iterativelyCommitPassiveMountEffects_begin(
}
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
} else {
nextEffect = fiber.return;
iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root);
}
} else {
+ warnIfWrongReturnPointer(fiber, firstChild);
nextEffect = firstChild;
}
} else {
@@ -1808,6 +1817,7 @@ function iterativelyCommitPassiveMountEffects_complete(
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -1886,6 +1896,7 @@ function iterativelyCommitPassiveUnmountEffects_begin() {
}
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
+ warnIfWrongReturnPointer(fiber, child);
nextEffect = child;
} else {
iterativelyCommitPassiveUnmountEffects_complete();
@@ -1904,6 +1915,7 @@ function iterativelyCommitPassiveUnmountEffects_complete() {
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -1941,6 +1953,7 @@ function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin(
const fiber = nextEffect;
const child = fiber.child;
if ((fiber.subtreeFlags & PassiveStatic) !== NoFlags && child !== null) {
+ warnIfWrongReturnPointer(fiber, child);
nextEffect = child;
} else {
iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
@@ -1968,6 +1981,7 @@ function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete(
const sibling = fiber.sibling;
if (sibling !== null) {
+ warnIfWrongReturnPointer(fiber.return, sibling);
nextEffect = sibling;
return;
}
@@ -3178,3 +3192,16 @@ function invokeEffectsInDev(
}
}
}
+
+let didWarnWrongReturnPointer = false;
+function warnIfWrongReturnPointer(returnFiber, child) {
+ if (__DEV__) {
+ if (!didWarnWrongReturnPointer && child.return !== returnFiber) {
+ didWarnWrongReturnPointer = true;
+ console.error(
+ 'Internal React error: Return pointer is inconsistent ' +
+ 'with parent.',
+ );
+ }
+ }
+}