@samitouri / QOS-React-2 / commits / 40e6029462

wrap SuspenseInstanceRetry callback so scheduler waits for it (#18805)

Fernando Lores committed May 5, 2020 at 12:47 UTC 40e6029462b23ce80c4c5fbd4aa422de20b7545e
3 files changed +82 -8
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+6 -4
@@ -198,6 +198,7 @@ import {
198 getWorkInProgressRoot,
199 pushRenderLanes,
200 } from './ReactFiberWorkLoop.new';
201 +import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
202
203 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
204
@@ -2336,10 +2337,11 @@ function updateDehydratedSuspenseComponent(
2337 // Leave the child in place. I.e. the dehydrated fragment.
2338 workInProgress.child = current.child;
2339 // Register a callback to retry this boundary once the server has sent the result.
2339 - registerSuspenseInstanceRetry(
2340 - suspenseInstance,
2341 - retryDehydratedSuspenseBoundary.bind(null, current),
2342 - );
2340 + let retry = retryDehydratedSuspenseBoundary.bind(null, current);
2341 + if (enableSchedulerTracing) {
2342 + retry = Schedule_tracing_wrap(retry);
2343 + }
2344 + registerSuspenseInstanceRetry(suspenseInstance, retry);
2345 return null;
2346 } else {
2347 // This is the first attempt.
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+6 -4
@@ -180,6 +180,7 @@ import {
180 markUnprocessedUpdateTime,
181 getWorkInProgressRoot,
182 } from './ReactFiberWorkLoop.old';
183 +import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
184
185 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
186
@@ -2303,10 +2304,11 @@ function updateDehydratedSuspenseComponent(
2304 // Leave the child in place. I.e. the dehydrated fragment.
2305 workInProgress.child = current.child;
2306 // Register a callback to retry this boundary once the server has sent the result.
2306 - registerSuspenseInstanceRetry(
2307 - suspenseInstance,
2308 - retryDehydratedSuspenseBoundary.bind(null, current),
2309 - );
2307 + let retry = retryDehydratedSuspenseBoundary.bind(null, current);
2308 + if (enableSchedulerTracing) {
2309 + retry = Schedule_tracing_wrap(retry);
2310 + }
2311 + registerSuspenseInstanceRetry(suspenseInstance, retry);
2312 return null;
2313 } else {
2314 // This is the first attempt.
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+70
@@ -719,6 +719,76 @@ describe('ReactDOMTracing', () => {
719 ).toHaveBeenLastNotifiedOfInteraction(interaction);
720 });
721
722 + // @gate experimental
723 + it('traces interaction across suspended hydration from server', async () => {
724 + // Copied from ReactDOMHostConfig.js
725 + const SUSPENSE_START_DATA = '$';
726 + const SUSPENSE_PENDING_START_DATA = '$?';
727 +
728 + const ref = React.createRef();
729 +
730 + function App() {
731 + return (
732 + <React.Suspense fallback="Loading...">
733 + <span ref={ref}>Hello</span>
734 + </React.Suspense>
735 + );
736 + }
737 +
738 + const container = document.createElement('div');
739 +
740 + // Render the final HTML.
741 + const finalHTML = ReactDOMServer.renderToString(<App />);
742 +
743 + // Replace the marker with a pending state.
744 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
745 + const escapedMarker = SUSPENSE_START_DATA.replace(
746 + /[.*+\-?^${}()|[\]\\]/g,
747 + '\\$&',
748 + );
749 + container.innerHTML = finalHTML.replace(
750 + new RegExp(escapedMarker, 'g'),
751 + SUSPENSE_PENDING_START_DATA,
752 + );
753 +
754 + let interaction;
755 +
756 + const root = ReactDOM.createRoot(container, {hydrate: true});
757 +
758 + // Start hydrating but simulate blocking for suspense data from the server.
759 + SchedulerTracing.unstable_trace('initialization', 0, () => {
760 + interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0];
761 +
762 + root.render(<App />);
763 + });
764 + Scheduler.unstable_flushAll();
765 + jest.runAllTimers();
766 +
767 + expect(ref.current).toBe(null);
768 + expect(onInteractionTraced).toHaveBeenCalledTimes(1);
769 + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction(
770 + interaction,
771 + );
772 + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
773 +
774 + // Unblock rendering, pretend the content is injected by the server.
775 + const startNode = container.childNodes[0];
776 + expect(startNode).not.toBe(null);
777 + expect(startNode.nodeType).toBe(Node.COMMENT_NODE);
778 +
779 + startNode.textContent = SUSPENSE_START_DATA;
780 + startNode._reactRetry();
781 +
782 + Scheduler.unstable_flushAll();
783 + jest.runAllTimers();
784 +
785 + expect(ref.current).not.toBe(null);
786 + expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
787 + expect(
788 + onInteractionScheduledWorkCompleted,
789 + ).toHaveBeenLastNotifiedOfInteraction(interaction);
790 + });
791 +
792 // @gate experimental
793 it('traces interaction across client-rendered hydration', () => {
794 let suspend = false;