@samitouri / QOS-React-2 / commits / 07d062dea8

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.

Sebastian Markbåge committed Aug 9, 2019 at 12:37 UTC 07d062dea852befa47d5b11111c21769bd8205d3
3 files changed +81 -4
packages/react-reconciler/src/ReactFiberBeginWork.js
+9 -1
@@ -1903,12 +1903,20 @@ function updateDehydratedSuspenseComponent(
1903 // they should be.
1904 let serverDisplayTime = requestCurrentTime();
1905 // 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;
1911 } else {
1912 // We'll continue hydrating the rest at offscreen priority since we'll already
1913 // be showing the right content coming from the server, it is no rush.
1914 workInProgress.expirationTime = Never;
1915 + if (enableSchedulerTracing) {
1916 + markSpawnedWork(Never);
1917 + }
1918 }
1919 +
1920 return null;
1921 }
1922
packages/react-reconciler/src/ReactFiberCompleteWork.js
-3
@@ -979,9 +979,6 @@ function completeWork(
979 'A dehydrated suspense component was completed without a hydrated node. ' +
980 'This is probably a bug in React.',
981 );
982 - if (enableSchedulerTracing) {
983 - markSpawnedWork(Never);
984 - }
982 skipPastDehydratedSuspenseInstance(workInProgress);
983 } else {
984 // We should never have been in a hydration state if we didn't have a current.
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+72
@@ -710,6 +710,78 @@ describe('ReactDOMTracing', () => {
710
711 done();
712 });
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 + });
785 });
786 });
787 });