@samitouri / QOS-React-2 / commits / 8da0da0937

Disable timeoutMs argument (#19703)

* Remove distinction between long, short transitions We're removing the `timeoutMs` option, so there's no longer any distinction between "short" and "long" transitions. They're all treated the same. This commit doesn't remove `timeoutMs` yet, only combines the internal priority levels. * Disable `timeoutMs` argument tl;dr ----- - We're removing the `timeoutMs` argument from `useTransition`. - Transitions will either immediately switch to a skeleton/placeholder view (when loading new content) or wait indefinitely until the data resolves (when refreshing stale content). - This commit disables the `timeoutMS` so that the API has the desired semantics. It doesn't yet update the types or migrate all the test callers. I'll do those steps in follow-up PRs. Motivation ---------- Currently, transitions initiated by `startTransition` / `useTransition` accept a `timeoutMs` option. You can use this to control the maximum amount of time that a transition is allowed to delay before we give up and show a placeholder. What we've discovered is that, in practice, every transition falls into one of two categories: a **load** or a **refresh**: - **Loading a new screen**: show the next screen as soon as possible, even if the data hasn't finished loading. Use a skeleton/placeholder UI to show progress. - **Refreshing a screen that's already visible**: keep showing the current screen indefinitely, for as long as it takes to load the fresh data, even if the current data is stale. Use a pending state (and maybe a busy indicator) to show progress. In other words, transitions should either *delay indefinitely* (for a refresh) or they should show a placeholder *instantly* (for a load). There's not much use for transitions that are delayed for a small-but-noticeable amount of time. So, the plan is to remove the `timeoutMs` option. Instead, we'll assign an effective timeout of `0` for loads, and `Infinity` for refreshes. The mechanism for distinguishing a load from a refresh already exists in the current model. If a component suspends, and the nearest Suspense boundary hasn't already mounted, we treat that as a load, because there's nothing on the screen. However, if the nearest boundary is mounted, we treat that as a refresh, since it's already showing content. If you need to fix a transition to be treated as a load instead of a refresh, or vice versa, the solution will involve rearranging the location of your Suspense boundaries. It may also involve adding a key. We're still working on proper documentation for these patterns. In the meantime, please reach out to us if you run into problems that you're unsure how to fix. We will remove `timeoutMs` from `useDeferredValue`, too, and apply the same load versus refresh semantics to the update that spawns the deferred value. Note that there are other types of delays that are not related to transitions; for example, we will still throttle the appearance of nested placeholders (we refer to this as the placeholder "train model"), and we may still apply a Just Noticeable Difference heuristic (JND) in some cases. These aren't going anywhere. (Well, the JND heuristic might but for different reasons than those discussed above.)

Andrew Clark committed Aug 26, 2020 at 16:35 UTC 8da0da0937af154b775b243c9d28b6aa50db696b
8 files changed +254 -372
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+13 -4
@@ -779,10 +779,19 @@ function runActTests(label, render, unmount, rerender) {
779 },
780 {timeout: 5000},
781 );
782 - // the spinner shows up regardless
783 - expect(
784 - document.querySelector('[data-test-id=spinner]'),
785 - ).not.toBeNull();
782 +
783 + if (label === 'concurrent mode') {
784 + // In Concurrent Mode, refresh transitions delay indefinitely.
785 + expect(document.querySelector('[data-test-id=spinner]')).toBeNull();
786 + } else {
787 + // In Legacy Mode and Blocking Mode, all fallbacks are forced to
788 + // display, even during a refresh transition.
789 + // TODO: Consider delaying indefinitely in Blocking Mode, to match
790 + // Concurrent Mode semantics.
791 + expect(
792 + document.querySelector('[data-test-id=spinner]'),
793 + ).not.toBeNull();
794 + }
795
796 // resolve the promise
797 await act(async () => {
packages/react-reconciler/src/ReactFiberLane.js
+40 -91
@@ -43,23 +43,20 @@ import {
43 NoPriority as NoSchedulerPriority,
44 } from './SchedulerWithReactIntegration.new';
45
46 -export const SyncLanePriority: LanePriority = 17;
47 -export const SyncBatchedLanePriority: LanePriority = 16;
46 +export const SyncLanePriority: LanePriority = 15;
47 +export const SyncBatchedLanePriority: LanePriority = 14;
48
49 -const InputDiscreteHydrationLanePriority: LanePriority = 15;
50 -export const InputDiscreteLanePriority: LanePriority = 14;
49 +const InputDiscreteHydrationLanePriority: LanePriority = 13;
50 +export const InputDiscreteLanePriority: LanePriority = 12;
51
52 -const InputContinuousHydrationLanePriority: LanePriority = 13;
53 -export const InputContinuousLanePriority: LanePriority = 12;
52 +const InputContinuousHydrationLanePriority: LanePriority = 11;
53 +export const InputContinuousLanePriority: LanePriority = 10;
54
55 -const DefaultHydrationLanePriority: LanePriority = 11;
56 -export const DefaultLanePriority: LanePriority = 10;
55 +const DefaultHydrationLanePriority: LanePriority = 9;
56 +export const DefaultLanePriority: LanePriority = 8;
57
58 -const TransitionShortHydrationLanePriority: LanePriority = 9;
59 -export const TransitionShortLanePriority: LanePriority = 8;
60 -
61 -const TransitionLongHydrationLanePriority: LanePriority = 7;
62 -export const TransitionLongLanePriority: LanePriority = 6;
58 +const TransitionHydrationPriority: LanePriority = 7;
59 +export const TransitionPriority: LanePriority = 6;
60
61 const RetryLanePriority: LanePriority = 5;
62
@@ -89,11 +86,8 @@ const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000
86 export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
87 export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
88
92 -const TransitionShortHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
93 -const TransitionShortLanes: Lanes = /* */ 0b0000000000000011110000000000000;
94 -
95 -const TransitionLongHydrationLane: Lane = /* */ 0b0000000000000100000000000000000;
96 -const TransitionLongLanes: Lanes = /* */ 0b0000000001111000000000000000000;
89 +const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
90 +const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;
91
92 const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
93
@@ -160,23 +154,14 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
154 return_highestLanePriority = DefaultLanePriority;
155 return defaultLanes;
156 }
163 - if ((lanes & TransitionShortHydrationLane) !== NoLanes) {
164 - return_highestLanePriority = TransitionShortHydrationLanePriority;
165 - return TransitionShortHydrationLane;
166 - }
167 - const transitionShortLanes = TransitionShortLanes & lanes;
168 - if (transitionShortLanes !== NoLanes) {
169 - return_highestLanePriority = TransitionShortLanePriority;
170 - return transitionShortLanes;
157 + if ((lanes & TransitionHydrationLane) !== NoLanes) {
158 + return_highestLanePriority = TransitionHydrationPriority;
159 + return TransitionHydrationLane;
160 }
172 - if ((lanes & TransitionLongHydrationLane) !== NoLanes) {
173 - return_highestLanePriority = TransitionLongHydrationLanePriority;
174 - return TransitionLongHydrationLane;
175 - }
176 - const transitionLongLanes = TransitionLongLanes & lanes;
177 - if (transitionLongLanes !== NoLanes) {
178 - return_highestLanePriority = TransitionLongLanePriority;
179 - return transitionLongLanes;
161 + const transitionLanes = TransitionLanes & lanes;
162 + if (transitionLanes !== NoLanes) {
163 + return_highestLanePriority = TransitionPriority;
164 + return transitionLanes;
165 }
166 const retryLanes = RetryLanes & lanes;
167 if (retryLanes !== NoLanes) {
@@ -241,10 +226,8 @@ export function lanePriorityToSchedulerPriority(
226 return UserBlockingSchedulerPriority;
227 case DefaultHydrationLanePriority:
228 case DefaultLanePriority:
244 - case TransitionShortHydrationLanePriority:
245 - case TransitionShortLanePriority:
246 - case TransitionLongHydrationLanePriority:
247 - case TransitionLongLanePriority:
229 + case TransitionHydrationPriority:
230 + case TransitionPriority:
231 case SelectiveHydrationLanePriority:
232 case RetryLanePriority:
233 return NormalSchedulerPriority;
@@ -402,7 +385,7 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
385 if (priority >= InputContinuousLanePriority) {
386 // User interactions should expire slightly more quickly.
387 return currentTime + 1000;
405 - } else if (priority >= TransitionLongLanePriority) {
388 + } else if (priority >= TransitionPriority) {
389 return currentTime + 5000;
390 } else {
391 // Anything idle priority or lower should never expire.
@@ -513,9 +496,7 @@ export function findUpdateLane(
496 if (lane === NoLane) {
497 // If all the default lanes are already being worked on, look for a
498 // lane in the transition range.
516 - lane = pickArbitraryLane(
517 - (TransitionShortLanes | TransitionLongLanes) & ~wipLanes,
518 - );
499 + lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
500 if (lane === NoLane) {
501 // All the transition lanes are taken, too. This should be very
502 // rare, but as a last resort, pick a default lane. This will have
@@ -525,8 +506,7 @@ export function findUpdateLane(
506 }
507 return lane;
508 }
528 - case TransitionShortLanePriority: // Should be handled by findTransitionLane instead
529 - case TransitionLongLanePriority:
509 + case TransitionPriority: // Should be handled by findTransitionLane instead
510 case RetryLanePriority: // Should be handled by findRetryLane instead
511 break;
512 case IdleLanePriority:
@@ -548,48 +528,21 @@ export function findUpdateLane(
528
529 // To ensure consistency across multiple updates in the same event, this should
530 // be pure function, so that it always returns the same lane for given inputs.
551 -export function findTransitionLane(
552 - lanePriority: LanePriority,
553 - wipLanes: Lanes,
554 - pendingLanes: Lanes,
555 -): Lane {
556 - if (lanePriority === TransitionShortLanePriority) {
557 - // First look for lanes that are completely unclaimed, i.e. have no
558 - // pending work.
559 - let lane = pickArbitraryLane(TransitionShortLanes & ~pendingLanes);
560 - if (lane === NoLane) {
561 - // If all lanes have pending work, look for a lane that isn't currently
562 - // being worked on.
563 - lane = pickArbitraryLane(TransitionShortLanes & ~wipLanes);
564 - if (lane === NoLane) {
565 - // If everything is being worked on, pick any lane. This has the
566 - // effect of interrupting the current work-in-progress.
567 - lane = pickArbitraryLane(TransitionShortLanes);
568 - }
569 - }
570 - return lane;
571 - }
572 - if (lanePriority === TransitionLongLanePriority) {
573 - // First look for lanes that are completely unclaimed, i.e. have no
574 - // pending work.
575 - let lane = pickArbitraryLane(TransitionLongLanes & ~pendingLanes);
531 +export function findTransitionLane(wipLanes: Lanes, pendingLanes: Lanes): Lane {
532 + // First look for lanes that are completely unclaimed, i.e. have no
533 + // pending work.
534 + let lane = pickArbitraryLane(TransitionLanes & ~pendingLanes);
535 + if (lane === NoLane) {
536 + // If all lanes have pending work, look for a lane that isn't currently
537 + // being worked on.
538 + lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
539 if (lane === NoLane) {
577 - // If all lanes have pending work, look for a lane that isn't currently
578 - // being worked on.
579 - lane = pickArbitraryLane(TransitionLongLanes & ~wipLanes);
580 - if (lane === NoLane) {
581 - // If everything is being worked on, pick any lane. This has the
582 - // effect of interrupting the current work-in-progress.
583 - lane = pickArbitraryLane(TransitionLongLanes);
584 - }
540 + // If everything is being worked on, pick any lane. This has the
541 + // effect of interrupting the current work-in-progress.
542 + lane = pickArbitraryLane(TransitionLanes);
543 }
586 - return lane;
544 }
588 - invariant(
589 - false,
590 - 'Invalid transition priority: %s. This is a bug in React.',
591 - lanePriority,
592 - );
545 + return lane;
546 }
547
548 // To ensure consistency across multiple updates in the same event, this should
@@ -816,18 +769,14 @@ export function getBumpedLaneForHydration(
769 case DefaultLanePriority:
770 lane = DefaultHydrationLane;
771 break;
819 - case TransitionShortHydrationLanePriority:
820 - case TransitionShortLanePriority:
821 - lane = TransitionShortHydrationLane;
822 - break;
823 - case TransitionLongHydrationLanePriority:
824 - case TransitionLongLanePriority:
825 - lane = TransitionLongHydrationLane;
772 + case TransitionHydrationPriority:
773 + case TransitionPriority:
774 + lane = TransitionHydrationLane;
775 break;
776 case RetryLanePriority:
777 // Shouldn't be reachable under normal circumstances, so there's no
778 // dedicated lane for retry priority. Use the one for long transitions.
830 - lane = TransitionLongHydrationLane;
779 + lane = TransitionHydrationLane;
780 break;
781 case SelectiveHydrationLanePriority:
782 lane = SelectiveHydrationLane;
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+24 -56
@@ -153,8 +153,6 @@ import {
153 SyncLanePriority,
154 SyncBatchedLanePriority,
155 InputDiscreteLanePriority,
156 - TransitionShortLanePriority,
157 - TransitionLongLanePriority,
156 DefaultLanePriority,
157 NoLanes,
158 NoLane,
@@ -457,24 +455,13 @@ export function requestUpdateLane(
455 // Use the size of the timeout as a heuristic to prioritize shorter
456 // transitions over longer ones.
457 // TODO: This will coerce numbers larger than 31 bits to 0.
460 - const timeoutMs = suspenseConfig.timeoutMs;
461 - const transitionLanePriority =
462 - timeoutMs === undefined || (timeoutMs | 0) < 10000
463 - ? TransitionShortLanePriority
464 - : TransitionLongLanePriority;
465 -
458 if (currentEventPendingLanes !== NoLanes) {
459 currentEventPendingLanes =
460 mostRecentlyUpdatedRoot !== null
461 ? mostRecentlyUpdatedRoot.pendingLanes
462 : NoLanes;
463 }
472 -
473 - return findTransitionLane(
474 - transitionLanePriority,
475 - currentEventWipLanes,
476 - currentEventPendingLanes,
477 - );
464 + return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
465 }
466
467 // TODO: Remove this dependency on the Scheduler priority.
@@ -936,52 +923,32 @@ function finishConcurrentRender(root, exitStatus, lanes) {
923 case RootSuspendedWithDelay: {
924 markRootSuspended(root, lanes);
925
939 - if (
940 - // do not delay if we're inside an act() scope
941 - !shouldForceFlushFallbacksInDEV()
942 - ) {
943 - // We're suspended in a state that should be avoided. We'll try to
944 - // avoid committing it for as long as the timeouts let us.
945 - const nextLanes = getNextLanes(root, NoLanes);
946 - if (nextLanes !== NoLanes) {
947 - // There's additional work on this root.
948 - break;
949 - }
950 - const suspendedLanes = root.suspendedLanes;
951 - if (!isSubsetOfLanes(suspendedLanes, lanes)) {
952 - // We should prefer to render the fallback of at the last
953 - // suspended level. Ping the last suspended level to try
954 - // rendering it again.
955 - // FIXME: What if the suspended lanes are Idle? Should not restart.
956 - const eventTime = requestEventTime();
957 - markRootPinged(root, suspendedLanes, eventTime);
958 - break;
959 - }
926 + if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
927 + // This is a transition, so we should exit without committing a
928 + // placeholder and without scheduling a timeout. Delay indefinitely
929 + // until we receive more data.
930 + // TODO: Check the lanes to see if it's a transition, instead of
931 + // tracking the latest timeout.
932 + break;
933 + }
934 +
935 + if (!shouldForceFlushFallbacksInDEV()) {
936 + // This is not a transition, but we did trigger an avoided state.
937 + // Schedule a placeholder to display after a short delay, using the Just
938 + // Noticable Difference.
939 + // TODO: Is the JND optimization worth the added complexity? If this is
940 + // the only reason we track the event time, then probably not.
941 + // Consider removing.
942
943 const mostRecentEventTime = getMostRecentEventTime(root, lanes);
962 - let msUntilTimeout;
963 - if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
964 - // We have processed a suspense config whose expiration time we
965 - // can use as the timeout.
966 - msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now();
967 - } else if (mostRecentEventTime === NoTimestamp) {
968 - // This should never normally happen because only new updates
969 - // cause delayed states, so we should have processed something.
970 - // However, this could also happen in an offscreen tree.
971 - msUntilTimeout = 0;
972 - } else {
973 - // If we didn't process a suspense config, compute a JND based on
974 - // the amount of time elapsed since the most recent event time.
975 - const eventTimeMs = mostRecentEventTime;
976 - const timeElapsedMs = now() - eventTimeMs;
977 - msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
978 - }
944 + const eventTimeMs = mostRecentEventTime;
945 + const timeElapsedMs = now() - eventTimeMs;
946 + const msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
947
948 // Don't bother with a very short suspense time.
949 if (msUntilTimeout > 10) {
982 - // The render is suspended, it hasn't timed out, and there's no
983 - // lower priority work to do. Instead of committing the fallback
984 - // immediately, wait for more data to arrive.
950 + // Instead of committing the fallback immediately, wait for more data
951 + // to arrive.
952 root.timeoutHandle = scheduleTimeout(
953 commitRoot.bind(null, root),
954 msUntilTimeout,
@@ -989,7 +956,8 @@ function finishConcurrentRender(root, exitStatus, lanes) {
956 break;
957 }
958 }
992 - // The work expired. Commit immediately.
959 +
960 + // Commit the placeholder.
961 commitRoot(root);
962 break;
963 }
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+24 -56
@@ -139,8 +139,6 @@ import {
139 SyncLanePriority,
140 SyncBatchedLanePriority,
141 InputDiscreteLanePriority,
142 - TransitionShortLanePriority,
143 - TransitionLongLanePriority,
142 DefaultLanePriority,
143 NoLanes,
144 NoLane,
@@ -445,24 +443,13 @@ export function requestUpdateLane(
443 // Use the size of the timeout as a heuristic to prioritize shorter
444 // transitions over longer ones.
445 // TODO: This will coerce numbers larger than 31 bits to 0.
448 - const timeoutMs = suspenseConfig.timeoutMs;
449 - const transitionLanePriority =
450 - timeoutMs === undefined || (timeoutMs | 0) < 10000
451 - ? TransitionShortLanePriority
452 - : TransitionLongLanePriority;
453 -
446 if (currentEventPendingLanes !== NoLanes) {
447 currentEventPendingLanes =
448 mostRecentlyUpdatedRoot !== null
449 ? mostRecentlyUpdatedRoot.pendingLanes
450 : NoLanes;
451 }
460 -
461 - return findTransitionLane(
462 - transitionLanePriority,
463 - currentEventWipLanes,
464 - currentEventPendingLanes,
465 - );
452 + return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
453 }
454
455 // TODO: Remove this dependency on the Scheduler priority.
@@ -924,52 +911,32 @@ function finishConcurrentRender(root, exitStatus, lanes) {
911 case RootSuspendedWithDelay: {
912 markRootSuspended(root, lanes);
913
927 - if (
928 - // do not delay if we're inside an act() scope
929 - !shouldForceFlushFallbacksInDEV()
930 - ) {
931 - // We're suspended in a state that should be avoided. We'll try to
932 - // avoid committing it for as long as the timeouts let us.
933 - const nextLanes = getNextLanes(root, NoLanes);
934 - if (nextLanes !== NoLanes) {
935 - // There's additional work on this root.
936 - break;
937 - }
938 - const suspendedLanes = root.suspendedLanes;
939 - if (!isSubsetOfLanes(suspendedLanes, lanes)) {
940 - // We should prefer to render the fallback of at the last
941 - // suspended level. Ping the last suspended level to try
942 - // rendering it again.
943 - // FIXME: What if the suspended lanes are Idle? Should not restart.
944 - const eventTime = requestEventTime();
945 - markRootPinged(root, suspendedLanes, eventTime);
946 - break;
947 - }
914 + if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
915 + // This is a transition, so we should exit without committing a
916 + // placeholder and without scheduling a timeout. Delay indefinitely
917 + // until we receive more data.
918 + // TODO: Check the lanes to see if it's a transition, instead of
919 + // tracking the latest timeout.
920 + break;
921 + }
922 +
923 + if (!shouldForceFlushFallbacksInDEV()) {
924 + // This is not a transition, but we did trigger an avoided state.
925 + // Schedule a placeholder to display after a short delay, using the Just
926 + // Noticable Difference.
927 + // TODO: Is the JND optimization worth the added complexity? If this is
928 + // the only reason we track the event time, then probably not.
929 + // Consider removing.
930
931 const mostRecentEventTime = getMostRecentEventTime(root, lanes);
950 - let msUntilTimeout;
951 - if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
952 - // We have processed a suspense config whose expiration time we
953 - // can use as the timeout.
954 - msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now();
955 - } else if (mostRecentEventTime === NoTimestamp) {
956 - // This should never normally happen because only new updates
957 - // cause delayed states, so we should have processed something.
958 - // However, this could also happen in an offscreen tree.
959 - msUntilTimeout = 0;
960 - } else {
961 - // If we didn't process a suspense config, compute a JND based on
962 - // the amount of time elapsed since the most recent event time.
963 - const eventTimeMs = mostRecentEventTime;
964 - const timeElapsedMs = now() - eventTimeMs;
965 - msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
966 - }
932 + const eventTimeMs = mostRecentEventTime;
933 + const timeElapsedMs = now() - eventTimeMs;
934 + const msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
935
936 // Don't bother with a very short suspense time.
937 if (msUntilTimeout > 10) {
970 - // The render is suspended, it hasn't timed out, and there's no
971 - // lower priority work to do. Instead of committing the fallback
972 - // immediately, wait for more data to arrive.
938 + // Instead of committing the fallback immediately, wait for more data
939 + // to arrive.
940 root.timeoutHandle = scheduleTimeout(
941 commitRoot.bind(null, root),
942 msUntilTimeout,
@@ -977,7 +944,8 @@ function finishConcurrentRender(root, exitStatus, lanes) {
944 break;
945 }
946 }
980 - // The work expired. Commit immediately.
947 +
948 + // Commit the placeholder.
949 commitRoot(root);
950 break;
951 }
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+74 -44
@@ -13,8 +13,9 @@
13 'use strict';
14
15 let React;
16 -let ReactCache;
17 -let TextResource;
16 +let textCache;
17 +let readText;
18 +let resolveText;
19 let ReactNoop;
20 let Scheduler;
21 let SchedulerTracing;
@@ -42,7 +43,6 @@ describe('ReactHooksWithNoopRenderer', () => {
43 ReactNoop = require('react-noop-renderer');
44 Scheduler = require('scheduler');
45 SchedulerTracing = require('scheduler/tracing');
45 - ReactCache = require('react-cache');
46 useState = React.useState;
47 useReducer = React.useReducer;
48 useEffect = React.useEffect;
@@ -58,27 +58,58 @@ describe('ReactHooksWithNoopRenderer', () => {
58 Suspense = React.Suspense;
59 act = ReactNoop.act;
60
61 - TextResource = ReactCache.unstable_createResource(
62 - ([text, ms = 0]) => {
63 - return new Promise((resolve, reject) =>
64 - setTimeout(() => {
65 - Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
66 - resolve(text);
67 - }, ms),
68 - );
69 - },
70 - ([text, ms]) => text,
71 - );
61 + textCache = new Map();
62 +
63 + readText = text => {
64 + const record = textCache.get(text);
65 + if (record !== undefined) {
66 + switch (record.status) {
67 + case 'pending':
68 + throw record.promise;
69 + case 'rejected':
70 + throw Error('Failed to load: ' + text);
71 + case 'resolved':
72 + return text;
73 + }
74 + } else {
75 + let ping;
76 + const promise = new Promise(resolve => (ping = resolve));
77 + const newRecord = {
78 + status: 'pending',
79 + ping: ping,
80 + promise,
81 + };
82 + textCache.set(text, newRecord);
83 + throw promise;
84 + }
85 + };
86 +
87 + resolveText = text => {
88 + const record = textCache.get(text);
89 + if (record !== undefined) {
90 + if (record.status === 'pending') {
91 + Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
92 + record.ping();
93 + record.ping = null;
94 + record.status = 'resolved';
95 + clearTimeout(record.promise._timer);
96 + record.promise = null;
97 + }
98 + } else {
99 + const newRecord = {
100 + ping: null,
101 + status: 'resolved',
102 + promise: null,
103 + };
104 + textCache.set(text, newRecord);
105 + }
106 + };
107 });
108
109 function span(prop) {
110 return {type: 'span', hidden: false, children: [], prop};
111 }
112
78 - function hiddenSpan(prop) {
79 - return {type: 'span', children: [], prop, hidden: true};
80 - }
81 -
113 function Text(props) {
114 Scheduler.unstable_yieldValue(props.text);
115 return <span prop={props.text} />;
@@ -87,12 +118,17 @@ describe('ReactHooksWithNoopRenderer', () => {
118 function AsyncText(props) {
119 const text = props.text;
120 try {
90 - TextResource.read([props.text, props.ms]);
121 + readText(text);
122 Scheduler.unstable_yieldValue(text);
123 return <span prop={text} />;
124 } catch (promise) {
125 if (typeof promise.then === 'function') {
126 Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
127 + if (typeof props.ms === 'number' && promise._timer === undefined) {
128 + promise._timer = setTimeout(() => {
129 + resolveText(text);
130 + }, props.ms);
131 + }
132 } else {
133 Scheduler.unstable_yieldValue(`Error! [${text}]`);
134 }
@@ -3235,7 +3271,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3271 <Suspense
3272 fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
3273 {show ? (
3238 - <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
3274 + <AsyncText text={`After... Pending: ${isPending}`} />
3275 ) : (
3276 <Text text={`Before... Pending: ${isPending}`} />
3277 )}
@@ -3265,15 +3301,14 @@ describe('ReactHooksWithNoopRenderer', () => {
3301 Scheduler.unstable_advanceTime(500);
3302 await advanceTimers(500);
3303
3268 - Scheduler.unstable_advanceTime(1000);
3269 - await advanceTimers(1000);
3304 + // Even after a long amount of time, we still don't show a placeholder.
3305 + Scheduler.unstable_advanceTime(100000);
3306 + await advanceTimers(100000);
3307 expect(ReactNoop.getChildren()).toEqual([
3271 - hiddenSpan('Before... Pending: true'),
3272 - span('Loading... Pending: false'),
3308 + span('Before... Pending: true'),
3309 ]);
3310
3275 - Scheduler.unstable_advanceTime(500);
3276 - await advanceTimers(500);
3311 + await resolveText('After... Pending: false');
3312 expect(Scheduler).toHaveYielded([
3313 'Promise resolved [After... Pending: false]',
3314 ]);
@@ -3283,6 +3318,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3318 ]);
3319 });
3320 });
3321 +
3322 // @gate experimental
3323 it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
3324 let transition;
@@ -3301,7 +3337,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3337 <Suspense
3338 fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
3339 {show ? (
3304 - <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
3340 + <AsyncText text={`After... Pending: ${isPending}`} />
3341 ) : (
3342 <Text text={`Before... Pending: ${isPending}`} />
3343 )}
@@ -3337,6 +3373,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3373 // result yet.
3374 Scheduler.unstable_advanceTime(1000);
3375 await advanceTimers(1000);
3376 + await resolveText('After... Pending: false');
3377 expect(Scheduler).toHaveYielded([
3378 'Promise resolved [After... Pending: false]',
3379 ]);
@@ -3364,9 +3401,9 @@ describe('ReactHooksWithNoopRenderer', () => {
3401
3402 describe('useDeferredValue', () => {
3403 // @gate experimental
3367 - it('defers text value until specified timeout', async () => {
3404 + it('defers text value', async () => {
3405 function TextBox({text}) {
3369 - return <AsyncText ms={1000} text={text} />;
3406 + return <AsyncText text={text} />;
3407 }
3408
3409 let _setText;
@@ -3393,8 +3430,7 @@ describe('ReactHooksWithNoopRenderer', () => {
3430 expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
3431 expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]);
3432
3396 - Scheduler.unstable_advanceTime(1000);
3397 - await advanceTimers(1000);
3433 + await resolveText('A');
3434 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
3435 expect(Scheduler).toFlushAndYield(['A']);
3436 expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
@@ -3419,22 +3455,16 @@ describe('ReactHooksWithNoopRenderer', () => {
3455 expect(Scheduler).toHaveYielded([]);
3456 expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
3457
3422 - await act(async () => {
3423 - Scheduler.unstable_advanceTime(500);
3424 - await advanceTimers(500);
3425 - });
3426 - expect(Scheduler).toHaveYielded([]);
3427 - expect(ReactNoop.getChildren()).toEqual([
3428 - span('B'),
3429 - hiddenSpan('A'),
3430 - span('Loading'),
3431 - ]);
3458 + // Even after a long amount of time, we don't show a fallback
3459 + Scheduler.unstable_advanceTime(100000);
3460 + await advanceTimers(100000);
3461 + expect(Scheduler).toFlushAndYield([]);
3462 + expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
3463
3464 await act(async () => {
3434 - Scheduler.unstable_advanceTime(250);
3435 - await advanceTimers(250);
3465 + await resolveText('B');
3466 });
3437 - expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B']);
3467 + expect(Scheduler).toHaveYielded(['Promise resolved [B]', 'B', 'B']);
3468 expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
3469 });
3470 });
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+1 -1
@@ -17,7 +17,7 @@ let Scheduler;
17 // Copied from ReactFiberLanes. Don't do this!
18 // This is hard coded directly to avoid needing to import, and
19 // we'll remove this as we replace runWithPriority with React APIs.
20 -const InputContinuousLanePriority = 12;
20 +const InputContinuousLanePriority = 10;
21
22 describe('ReactIncrementalUpdates', () => {
23 beforeEach(() => {
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+78 -119
@@ -2355,17 +2355,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2355 );
2356
2357 expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2358 - Scheduler.unstable_advanceTime(1000);
2359 - await advanceTimers(1000);
2360 - // Even after a second, we have still not yet flushed the loading state.
2358 + Scheduler.unstable_advanceTime(100000);
2359 + await advanceTimers(100000);
2360 + // Even after lots of time has passed, we have still not yet flushed the
2361 + // loading state.
2362 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2362 - Scheduler.unstable_advanceTime(1100);
2363 - await advanceTimers(1100);
2364 - // After the timeout, we do show the loading state.
2365 - expect(ReactNoop.getChildren()).toEqual([
2366 - hiddenSpan('A'),
2367 - span('Loading...'),
2368 - ]);
2363 // Later we load the data.
2364 Scheduler.unstable_advanceTime(3000);
2365 await advanceTimers(3000);
@@ -2385,7 +2379,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2379 }
2380 return (
2381 <Suspense fallback={<Text text="Loading..." />}>
2388 - <AsyncText text={page} ms={5000} />
2382 + <AsyncText text={page} />
2383 </Suspense>
2384 );
2385 }
@@ -2408,8 +2402,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2402 });
2403
2404 // Later we load the data.
2411 - Scheduler.unstable_advanceTime(5000);
2412 - await advanceTimers(5000);
2405 + await resolveText('A');
2406 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2407 expect(Scheduler).toFlushAndYield(['A']);
2408 expect(ReactNoop.getChildren()).toEqual([span('A')]);
@@ -2422,21 +2415,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2415 );
2416
2417 expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2425 - Scheduler.unstable_advanceTime(1000);
2426 - await advanceTimers(1000);
2427 - // Even after a second, we have still not yet flushed the loading state.
2418 + Scheduler.unstable_advanceTime(100000);
2419 + await advanceTimers(100000);
2420 + // Even after lots of time has passed, we have still not yet flushed the
2421 + // loading state.
2422 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2429 - Scheduler.unstable_advanceTime(1100);
2430 - await advanceTimers(1100);
2431 - // After the timeout, we do show the loading state.
2432 - expect(ReactNoop.getChildren()).toEqual([
2433 - hiddenSpan('A'),
2434 - span('Loading...'),
2435 - ]);
2423 });
2424 // Later we load the data.
2438 - Scheduler.unstable_advanceTime(3000);
2439 - await advanceTimers(3000);
2425 + await resolveText('B');
2426 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2427 expect(Scheduler).toFlushAndYield(['B']);
2428 expect(ReactNoop.getChildren()).toEqual([span('B')]);
@@ -2455,7 +2441,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2441 }
2442 return (
2443 <Suspense fallback={<Text text="Loading..." />}>
2458 - <AsyncText text={page} ms={5000} />
2444 + <AsyncText text={page} />
2445 </Suspense>
2446 );
2447 }
@@ -2479,8 +2465,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2465 });
2466
2467 // Later we load the data.
2482 - Scheduler.unstable_advanceTime(5000);
2483 - await advanceTimers(5000);
2468 + await resolveText('A');
2469 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2470 expect(Scheduler).toFlushAndYield(['A']);
2471 expect(ReactNoop.getChildren()).toEqual([span('A')]);
@@ -2493,21 +2478,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2478 );
2479
2480 expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2496 - Scheduler.unstable_advanceTime(1000);
2497 - await advanceTimers(1000);
2498 - // Even after a second, we have still not yet flushed the loading state.
2481 + Scheduler.unstable_advanceTime(100000);
2482 + await advanceTimers(100000);
2483 + // Even after lots of time has passed, we have still not yet flushed the
2484 + // loading state.
2485 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2500 - Scheduler.unstable_advanceTime(1100);
2501 - await advanceTimers(1100);
2502 - // After the timeout, we do show the loading state.
2503 - expect(ReactNoop.getChildren()).toEqual([
2504 - hiddenSpan('A'),
2505 - span('Loading...'),
2506 - ]);
2486 });
2487 // Later we load the data.
2509 - Scheduler.unstable_advanceTime(3000);
2510 - await advanceTimers(3000);
2488 + await resolveText('B');
2489 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2490 expect(Scheduler).toFlushAndYield(['B']);
2491 expect(ReactNoop.getChildren()).toEqual([span('B')]);
@@ -2520,7 +2498,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2498 function App({page}) {
2499 return (
2500 <Suspense fallback={<Text text="Loading..." />}>
2523 - <AsyncText text={page} ms={5000} />
2501 + <AsyncText text={page} />
2502 </Suspense>
2503 );
2504 }
@@ -2535,8 +2513,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2513 expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
2514
2515 // Later we load the data.
2538 - Scheduler.unstable_advanceTime(5000);
2539 - await advanceTimers(5000);
2516 + await resolveText('A');
2517 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2518 expect(Scheduler).toFlushAndYield(['A']);
2519 expect(ReactNoop.getChildren()).toEqual([span('A')]);
@@ -2552,8 +2529,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2529 expect(ReactNoop.getChildren()).toEqual([span('A')]);
2530
2531 // Later we load the data.
2555 - Scheduler.unstable_advanceTime(3000);
2556 - await advanceTimers(3000);
2532 + await resolveText('B');
2533 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2534 expect(Scheduler).toFlushAndYield(['B']);
2535 expect(ReactNoop.getChildren()).toEqual([span('B')]);
@@ -2562,14 +2538,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2538 React.unstable_startTransition(() => ReactNoop.render(<App page="C" />));
2539 expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
2540
2565 - // Advance past the current (effectively) infinite timeout.
2566 - // This is enforcing temporary behavior until it's truly infinite.
2541 + // Even after lots of time has passed, we have still not yet flushed the
2542 + // loading state.
2543 Scheduler.unstable_advanceTime(100000);
2544 await advanceTimers(100000);
2569 - expect(ReactNoop.getChildren()).toEqual([
2570 - hiddenSpan('B'),
2571 - span('Loading...'),
2572 - ]);
2545 + expect(ReactNoop.getChildren()).toEqual([span('B')]);
2546 });
2547
2548 // @gate experimental
@@ -2583,7 +2556,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2556 }
2557 return (
2558 <Suspense fallback={<Text text="Loading..." />}>
2586 - <AsyncText text={page} ms={5000} />
2559 + <AsyncText text={page} />
2560 </Suspense>
2561 );
2562 }
@@ -2603,8 +2576,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2576 });
2577
2578 // Later we load the data.
2606 - Scheduler.unstable_advanceTime(5000);
2607 - await advanceTimers(5000);
2579 + await resolveText('A');
2580 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2581 expect(Scheduler).toFlushAndYield(['A']);
2582 expect(ReactNoop.getChildren()).toEqual([span('A')]);
@@ -2623,8 +2595,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2595 });
2596
2597 // Later we load the data.
2626 - Scheduler.unstable_advanceTime(3000);
2627 - await advanceTimers(3000);
2598 + await resolveText('B');
2599 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2600 expect(Scheduler).toFlushAndYield(['B']);
2601 expect(ReactNoop.getChildren()).toEqual([span('B')]);
@@ -2635,14 +2606,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2606
2607 expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
2608
2638 - // Advance past the current effectively infinite timeout.
2639 - // This is enforcing temporary behavior until it's truly infinite.
2609 + // Even after lots of time has passed, we have still not yet flushed the
2610 + // loading state.
2611 Scheduler.unstable_advanceTime(100000);
2612 await advanceTimers(100000);
2642 - expect(ReactNoop.getChildren()).toEqual([
2643 - hiddenSpan('B'),
2644 - span('Loading...'),
2645 - ]);
2613 + expect(ReactNoop.getChildren()).toEqual([span('B')]);
2614 });
2615 });
2616
@@ -2711,14 +2679,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2679
2680 expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
2681
2714 - // Advance past the current effectively infinite timeout.
2715 - // This is enforcing temporary behavior until it's truly infinite.
2682 + // Even after lots of time has passed, we have still not yet flushed the
2683 + // loading state.
2684 Scheduler.unstable_advanceTime(100000);
2685 await advanceTimers(100000);
2718 - expect(ReactNoop.getChildren()).toEqual([
2719 - hiddenSpan('B'),
2720 - span('Loading...'),
2721 - ]);
2686 + expect(ReactNoop.getChildren()).toEqual([span('B')]);
2687 });
2688 });
2689 });
@@ -2785,21 +2750,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2750 );
2751 expect(Scheduler).toFlushAndYield(['B', 'Suspend! [C]', 'Loading...']);
2752 expect(ReactNoop.getChildren()).toEqual([span('B')]);
2788 - Scheduler.unstable_advanceTime(1200);
2789 - await advanceTimers(1200);
2790 - // Even after a second, we have still not yet flushed the loading state.
2791 - expect(ReactNoop.getChildren()).toEqual([span('B')]);
2753 + // Event after a large amount of time, we never show a loading state.
2754 Scheduler.unstable_advanceTime(60000);
2755 await advanceTimers(60000);
2794 - // After the timeout we show the loading state.
2795 - expect(ReactNoop.getChildren()).toEqual([
2796 - hiddenSpan('B'),
2797 - span('Loading...'),
2798 - ]);
2756 + expect(ReactNoop.getChildren()).toEqual([span('B')]);
2757 });
2758
2759 // @gate experimental
2802 - it('withSuspenseConfig timeout applies when we use an updated avoided boundary', async () => {
2760 + it('withSuspenseConfig delay applies when we use an updated avoided boundary', async () => {
2761 function App({page}) {
2762 return (
2763 <Suspense fallback={<Text text="Loading..." />}>
@@ -2807,7 +2765,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2765 <Suspense
2766 fallback={<Text text={'Loading ' + page + '...'} />}
2767 unstable_avoidThisFallback={true}>
2810 - <AsyncText text={page} ms={3000} />
2768 + <AsyncText text={page} />
2769 </Suspense>
2770 </Suspense>
2771 );
@@ -2816,8 +2774,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2774 // Initial render.
2775 ReactNoop.render(<App page="A" />);
2776 expect(Scheduler).toFlushAndYield(['Hi!', 'Suspend! [A]', 'Loading...']);
2819 - Scheduler.unstable_advanceTime(3000);
2820 - await advanceTimers(3000);
2777 + await resolveText('A');
2778 expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2779 expect(Scheduler).toFlushAndYield(['Hi!', 'A']);
2780 expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
@@ -2837,18 +2794,19 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2794 expect(Scheduler).toFlushAndYield([]);
2795 // We should still be suspended here because this loading state should be avoided.
2796 expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2840 - Scheduler.unstable_advanceTime(1500);
2841 - await advanceTimers(1500);
2797 + await resolveText('B');
2798 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2843 - expect(ReactNoop.getChildren()).toEqual([
2844 - span('Hi!'),
2845 - hiddenSpan('A'),
2846 - span('Loading B...'),
2847 - ]);
2799 + expect(Scheduler).toFlushAndYield(['Hi!', 'B']);
2800 + expect(ReactNoop).toMatchRenderedOutput(
2801 + <>
2802 + <span prop="Hi!" />
2803 + <span prop="B" />
2804 + </>,
2805 + );
2806 });
2807
2808 // @gate experimental
2851 - it('withSuspenseConfig timeout applies when we use a newly created avoided boundary', async () => {
2809 + it('withSuspenseConfig delay applies when we use a newly created avoided boundary', async () => {
2810 function App({page}) {
2811 return (
2812 <Suspense fallback={<Text text="Loading..." />}>
@@ -2859,7 +2817,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2817 <Suspense
2818 fallback={<Text text={'Loading ' + page + '...'} />}
2819 unstable_avoidThisFallback={true}>
2862 - <AsyncText text={page} ms={3000} />
2820 + <AsyncText text={page} />
2821 </Suspense>
2822 )}
2823 </Suspense>
@@ -2885,14 +2843,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2843 await advanceTimers(1800);
2844 expect(Scheduler).toFlushAndYield([]);
2845 // We should still be suspended here because this loading state should be avoided.
2888 - expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2889 - Scheduler.unstable_advanceTime(1500);
2890 - await advanceTimers(1500);
2846 + expect(ReactNoop).toMatchRenderedOutput(
2847 + <>
2848 + <span prop="Hi!" />
2849 + <span prop="A" />
2850 + </>,
2851 + );
2852 + await resolveText('B');
2853 expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2892 - expect(ReactNoop.getChildren()).toEqual([
2893 - span('Hi!'),
2894 - span('Loading B...'),
2895 - ]);
2854 + expect(Scheduler).toFlushAndYield(['Hi!', 'B']);
2855 + expect(ReactNoop).toMatchRenderedOutput(
2856 + <>
2857 + <span prop="Hi!" />
2858 + <span prop="B" />
2859 + </>,
2860 + );
2861 });
2862
2863 // @gate experimental
@@ -3934,19 +3899,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3899 </>,
3900 );
3901
3937 - // Commit the placeholder
3938 - Scheduler.unstable_advanceTime(20000);
3939 - await advanceTimers(20000);
3940 -
3941 - expect(root).toMatchRenderedOutput(
3942 - <>
3943 - <span hidden={true} prop="A1" />
3944 - <span prop="Loading..." />
3945 - <span hidden={true} prop="B" />
3946 - <span prop="Loading..." />
3947 - </>,
3948 - );
3902 + await resolveText('A2');
3903 + await resolveText('B2');
3904 });
3905 + expect(Scheduler).toHaveYielded([
3906 + 'Promise resolved [A2]',
3907 + 'Promise resolved [B2]',
3908 + 'A2',
3909 + 'B2',
3910 + ]);
3911 + expect(root).toMatchRenderedOutput(
3912 + <>
3913 + <span prop="A2" />
3914 + <span prop="B2" />
3915 + </>,
3916 + );
3917 });
3918
3919 // Regression: https://github.com/facebook/react/issues/18486
@@ -4030,9 +3997,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3997 // Neither is resolved yet.
3998 'Pending...',
3999 '',
4033 - 'Pending...',
4034 - 'Suspend! [a]',
4035 - 'Loading...',
4000 'Suspend! [b]',
4001 'Loading...',
4002 ]);
@@ -4050,16 +4014,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
4014 await resolveText('a');
4015
4016 expect(Scheduler).toHaveYielded(['Promise resolved [a]']);
4053 - expect(Scheduler).toFlushAndYield([
4054 - 'Pending...',
4055 - 'a',
4056 - 'Suspend! [b]',
4057 - 'Loading...',
4058 - ]);
4017 + expect(Scheduler).toFlushAndYield(['Suspend! [b]', 'Loading...']);
4018 expect(root).toMatchRenderedOutput(
4019 <>
4020 <span prop="Pending..." />
4062 - <span prop="a" />
4021 + <span prop="" />
4022 </>,
4023 );
4024
scripts/error-codes/codes.json
-1
@@ -348,7 +348,6 @@
348 "356": "Could not read the cache.",
349 "357": "The current renderer does not support React Scopes. This error is likely caused by a bug in React. Please file an issue.",
350 "358": "Invalid update priority: %s. This is a bug in React.",
351 - "359": "Invalid transition priority: %s. This is a bug in React.",
351 "360": "Invalid lane: %s. This is a bug in React.",
352 "361": "The current renderer does not support test selectors. This error is likely caused by a bug in React. Please file an issue.",
353 "362": "Could not find React container within specified host subtree.",