Skip to content

Commit 07d062d

Browse files
authored
Mark spawned work for client-rendered suspense boundary (#16341)
Currently this is getting marked as Never which is the normal continuation for a dehydrated boundary, but if it is client-rendered it has a higher priority. That causes us to drop the interaction tracing for that render. This colocates the marking where we actually set the expiration time.
1 parent 07a02fb commit 07d062d

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1903,12 +1903,20 @@ function updateDehydratedSuspenseComponent(
19031903
// they should be.
19041904
let serverDisplayTime = requestCurrentTime();
19051905
// Schedule a normal pri update to render this content.
1906-
workInProgress.expirationTime = computeAsyncExpiration(serverDisplayTime);
1906+
let newExpirationTime = computeAsyncExpiration(serverDisplayTime);
1907+
if (enableSchedulerTracing) {
1908+
markSpawnedWork(newExpirationTime);
1909+
}
1910+
workInProgress.expirationTime = newExpirationTime;
19071911
} else {
19081912
// We'll continue hydrating the rest at offscreen priority since we'll already
19091913
// be showing the right content coming from the server, it is no rush.
19101914
workInProgress.expirationTime = Never;
1915+
if (enableSchedulerTracing) {
1916+
markSpawnedWork(Never);
1917+
}
19111918
}
1919+
19121920
return null;
19131921
}
19141922

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -979,9 +979,6 @@ function completeWork(
979979
'A dehydrated suspense component was completed without a hydrated node. ' +
980980
'This is probably a bug in React.',
981981
);
982-
if (enableSchedulerTracing) {
983-
markSpawnedWork(Never);
984-
}
985982
skipPastDehydratedSuspenseInstance(workInProgress);
986983
} else {
987984
// We should never have been in a hydration state if we didn't have a current.

packages/react/src/__tests__/ReactDOMTracing-test.internal.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,78 @@ describe('ReactDOMTracing', () => {
710710

711711
done();
712712
});
713+
714+
it('traces interaction across client-rendered hydration', async done => {
715+
let suspend = false;
716+
let promise = new Promise(() => {});
717+
let ref = React.createRef();
718+
719+
function Child() {
720+
if (suspend) {
721+
throw promise;
722+
} else {
723+
return 'Hello';
724+
}
725+
}
726+
727+
function App() {
728+
return (
729+
<div>
730+
<React.Suspense fallback="Loading...">
731+
<span ref={ref}>
732+
<Child />
733+
</span>
734+
</React.Suspense>
735+
</div>
736+
);
737+
}
738+
739+
// Render the final HTML.
740+
suspend = true;
741+
const finalHTML = ReactDOMServer.renderToString(<App />);
742+
743+
const container = document.createElement('div');
744+
container.innerHTML = finalHTML;
745+
746+
let interaction;
747+
748+
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
749+
750+
// Hydrate without suspending to fill in the client-rendered content.
751+
suspend = false;
752+
SchedulerTracing.unstable_trace('initialization', 0, () => {
753+
interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
754+
755+
root.render(<App />);
756+
});
757+
758+
expect(onWorkStopped).toHaveBeenCalledTimes(1);
759+
760+
// Advance time a bit so that we get into a new expiration bucket.
761+
Scheduler.unstable_advanceTime(300);
762+
jest.advanceTimersByTime(300);
763+
764+
Scheduler.unstable_flushAll();
765+
jest.runAllTimers();
766+
767+
expect(ref.current).not.toBe(null);
768+
769+
// We should've had two commits that was traced.
770+
// First one that hydrates the parent, and then one that hydrates
771+
// the boundary at higher than Never priority.
772+
expect(onWorkStopped).toHaveBeenCalledTimes(3);
773+
774+
expect(onInteractionTraced).toHaveBeenCalledTimes(1);
775+
expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
776+
interaction,
777+
);
778+
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
779+
expect(
780+
onInteractionScheduledWorkCompleted,
781+
).toHaveBeenLastNotifiedOfInteraction(interaction);
782+
783+
done();
784+
});
713785
});
714786
});
715787
});

0 commit comments

Comments
 (0)