diff --git a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
index c8384b2fa42..184a141c323 100644
--- a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
+++ b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
@@ -233,4 +233,87 @@ describe('ProfilerStore', () => {
utils.act(() => render());
utils.act(() => store.profilerStore.startProfiling());
});
+
+ // @reactVersion >= 18.2
+ // Verifies that cached JSX children (via useMemoCache) are not reported as rendered.
+ it('should not report cached/memoized children as rendered when parent re-renders', () => {
+ const Scheduler = require('scheduler');
+ const useMemoCache = require('react/compiler-runtime').c;
+ let forceTextUpdate;
+
+ function Child({count}: {count: number}) {
+ Scheduler.unstable_advanceTime(10);
+ return
Count: {count}
;
+ }
+
+ function Parent() {
+ const $ = useMemoCache(4);
+
+ const [count] = React.useState(0);
+ const [text, setText] = React.useState('');
+
+ forceTextUpdate = setText;
+
+ Scheduler.unstable_advanceTime(5);
+
+ let t0;
+ if ($[0] !== count) {
+ t0 = (
+
+
+
+ );
+ $[0] = count;
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+
+ let t1;
+ if ($[2] !== text || $[3] !== t0) {
+ t1 = (
+
+ {text}
+ {t0}
+
+ );
+ $[2] = text;
+ $[3] = t0;
+ } else {
+ t1 = $[3];
+ }
+
+ return t1;
+ }
+
+ utils.act(() => render());
+ utils.act(() => store.profilerStore.startProfiling());
+
+ // Updating text should not cause Child to be reported as rendered,
+ // because the cached child JSX (t0) stays the same.
+ utils.act(() => forceTextUpdate('a'));
+
+ utils.act(() => store.profilerStore.stopProfiling());
+
+ const rootID = store.roots[0];
+ const data = store.profilerStore.getDataForRoot(rootID);
+
+ expect(data.commitData).toHaveLength(1);
+
+ const commit = data.commitData[0];
+ const renderedFiberIds = Array.from(commit.fiberActualDurations.keys());
+
+ let childElementId = null;
+ for (let i = 0; i < store.numElements; i++) {
+ const element = store.getElementAtIndex(i);
+ if (element?.displayName === 'Child') {
+ childElementId = element.id;
+ break;
+ }
+ }
+
+ expect(childElementId).not.toBeNull();
+ // Child should NOT be in fiberActualDurations because the fiber was never visited.
+ expect(renderedFiberIds).not.toContain(childElementId);
+ });
});
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 289bad6f329..c6ada5809b8 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -2086,6 +2086,12 @@ export function attach(
}
function didFiberRender(prevFiber: Fiber, nextFiber: Fiber): boolean {
+ // Note: prevFiber === nextFiber means the fiber was never visited this render.
+ // Any PerformedWork flag is stale because the parent bailed out entirely.
+ if (prevFiber === nextFiber) {
+ return false;
+ }
+
switch (nextFiber.tag) {
case ClassComponent:
case FunctionComponent: