@samitouri / QOS-React-1 / commits / 7319c61b18

[be] Remove unshipped experimental <Cache> element type (#28698)

Removes the `<Cache />` element type since we're going with a simpler caching strategy.

Joseph Savona committed Apr 2, 2024 at 07:57 UTC 7319c61b18274ee7e7c20bd2e533b93c922d8fe0
19 files changed -1643
packages/react-devtools-shared/src/__tests__/store-test.js
-2
@@ -1575,7 +1575,6 @@ describe('Store', () => {
1575 <FakeHigherOrderComponent />
1576 <MemoizedFakeHigherOrderComponent />
1577 <ForwardRefFakeHigherOrderComponent />
1578 - <React.unstable_Cache />
1578 <MemoizedFakeHigherOrderComponentWithDisplayNameOverride />
1579 <ForwardRefFakeHigherOrderComponentWithDisplayNameOverride />
1580 </React.Fragment>
@@ -1603,7 +1602,6 @@ describe('Store', () => {
1602 <Baz> [withFoo][withBar]
1603 <Baz> [Memo][withFoo][withBar]
1604 <Baz> [ForwardRef][withFoo][withBar]
1606 - <Cache>
1605 <memoRefOverride>
1606 <forwardRefOverride>
1607 `);
packages/react-devtools-shell/src/app/ElementTypes/index.js
-1
@@ -18,7 +18,6 @@ import {
18 Profiler,
19 StrictMode,
20 Suspense,
21 - unstable_Cache as Cache,
21 } from 'react';
22
23 const Context = createContext('abc');
packages/react-reconciler/src/ReactFiber.js
-20
@@ -28,7 +28,6 @@ import {
28 isHostSingletonType,
29 } from './ReactFiberConfig';
30 import {
31 - enableCache,
31 enableProfilerTimer,
32 enableScopeAPI,
33 enableLegacyHidden,
@@ -66,7 +65,6 @@ import {
65 ScopeComponent,
66 OffscreenComponent,
67 LegacyHiddenComponent,
69 - CacheComponent,
68 TracingMarkerComponent,
69 } from './ReactWorkTags';
70 import {OffscreenVisible} from './ReactFiberActivityComponent';
@@ -104,7 +102,6 @@ import {
102 REACT_SCOPE_TYPE,
103 REACT_OFFSCREEN_TYPE,
104 REACT_LEGACY_HIDDEN_TYPE,
107 - REACT_CACHE_TYPE,
105 REACT_TRACING_MARKER_TYPE,
106 } from 'shared/ReactSymbols';
107 import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent';
@@ -560,11 +557,6 @@ export function createFiberFromTypeAndProps(
557 return createFiberFromScope(type, pendingProps, mode, lanes, key);
558 }
559 // Fall through
563 - case REACT_CACHE_TYPE:
564 - if (enableCache) {
565 - return createFiberFromCache(pendingProps, mode, lanes, key);
566 - }
567 - // Fall through
560 case REACT_TRACING_MARKER_TYPE:
561 if (enableTransitionTracing) {
562 return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
@@ -807,18 +799,6 @@ export function createFiberFromLegacyHidden(
799 return fiber;
800 }
801
810 -export function createFiberFromCache(
811 - pendingProps: any,
812 - mode: TypeOfMode,
813 - lanes: Lanes,
814 - key: null | string,
815 -): Fiber {
816 - const fiber = createFiber(CacheComponent, pendingProps, key, mode);
817 - fiber.elementType = REACT_CACHE_TYPE;
818 - fiber.lanes = lanes;
819 - return fiber;
820 -}
821 -
802 export function createFiberFromTracingMarker(
803 pendingProps: any,
804 mode: TypeOfMode,
packages/react-reconciler/src/__tests__/ReactCacheElement-test.js deleted
-1597
@@ -1,1597 +0,0 @@
1 -let React;
2 -let ReactNoop;
3 -let Cache;
4 -let getCacheSignal;
5 -let getCacheForType;
6 -let Scheduler;
7 -let assertLog;
8 -let act;
9 -let Suspense;
10 -let Activity;
11 -let useCacheRefresh;
12 -let startTransition;
13 -let useState;
14 -
15 -let textCaches;
16 -let seededCache;
17 -
18 -describe('ReactCacheElement', () => {
19 - beforeEach(() => {
20 - jest.resetModules();
21 -
22 - React = require('react');
23 - ReactNoop = require('react-noop-renderer');
24 - Cache = React.unstable_Cache;
25 - Scheduler = require('scheduler');
26 - act = require('internal-test-utils').act;
27 - Suspense = React.Suspense;
28 - Activity = React.unstable_Activity;
29 - getCacheSignal = React.unstable_getCacheSignal;
30 - getCacheForType = React.unstable_getCacheForType;
31 - useCacheRefresh = React.unstable_useCacheRefresh;
32 - startTransition = React.startTransition;
33 - useState = React.useState;
34 -
35 - const InternalTestUtils = require('internal-test-utils');
36 - assertLog = InternalTestUtils.assertLog;
37 -
38 - textCaches = [];
39 - seededCache = null;
40 - });
41 -
42 - function createTextCache() {
43 - if (seededCache !== null) {
44 - // Trick to seed a cache before it exists.
45 - // TODO: Need a built-in API to seed data before the initial render (i.e.
46 - // not a refresh because nothing has mounted yet).
47 - const textCache = seededCache;
48 - seededCache = null;
49 - return textCache;
50 - }
51 -
52 - const data = new Map();
53 - const version = textCaches.length + 1;
54 - const textCache = {
55 - version,
56 - data,
57 - resolve(text) {
58 - const record = data.get(text);
59 - if (record === undefined) {
60 - const newRecord = {
61 - status: 'resolved',
62 - value: text,
63 - cleanupScheduled: false,
64 - };
65 - data.set(text, newRecord);
66 - } else if (record.status === 'pending') {
67 - record.value.resolve();
68 - }
69 - },
70 - reject(text, error) {
71 - const record = data.get(text);
72 - if (record === undefined) {
73 - const newRecord = {
74 - status: 'rejected',
75 - value: error,
76 - cleanupScheduled: false,
77 - };
78 - data.set(text, newRecord);
79 - } else if (record.status === 'pending') {
80 - record.value.reject();
81 - }
82 - },
83 - };
84 - textCaches.push(textCache);
85 - return textCache;
86 - }
87 -
88 - function readText(text) {
89 - const signal = getCacheSignal ? getCacheSignal() : null;
90 - const textCache = getCacheForType(createTextCache);
91 - const record = textCache.data.get(text);
92 - if (record !== undefined) {
93 - if (!record.cleanupScheduled) {
94 - // This record was seeded prior to the abort signal being available:
95 - // schedule a cleanup function for it.
96 - // TODO: Add ability to cleanup entries seeded w useCacheRefresh()
97 - record.cleanupScheduled = true;
98 - if (getCacheSignal) {
99 - signal.addEventListener('abort', () => {
100 - Scheduler.log(`Cache cleanup: ${text} [v${textCache.version}]`);
101 - });
102 - }
103 - }
104 - switch (record.status) {
105 - case 'pending':
106 - throw record.value;
107 - case 'rejected':
108 - throw record.value;
109 - case 'resolved':
110 - return textCache.version;
111 - }
112 - } else {
113 - Scheduler.log(`Cache miss! [${text}]`);
114 -
115 - let resolve;
116 - let reject;
117 - const thenable = new Promise((res, rej) => {
118 - resolve = res;
119 - reject = rej;
120 - }).then(
121 - value => {
122 - if (newRecord.status === 'pending') {
123 - newRecord.status = 'resolved';
124 - newRecord.value = value;
125 - }
126 - },
127 - error => {
128 - if (newRecord.status === 'pending') {
129 - newRecord.status = 'rejected';
130 - newRecord.value = error;
131 - }
132 - },
133 - );
134 - thenable.resolve = resolve;
135 - thenable.reject = reject;
136 -
137 - const newRecord = {
138 - status: 'pending',
139 - value: thenable,
140 - cleanupScheduled: true,
141 - };
142 - textCache.data.set(text, newRecord);
143 -
144 - if (getCacheSignal) {
145 - signal.addEventListener('abort', () => {
146 - Scheduler.log(`Cache cleanup: ${text} [v${textCache.version}]`);
147 - });
148 - }
149 - throw thenable;
150 - }
151 - }
152 -
153 - function Text({text}) {
154 - Scheduler.log(text);
155 - return text;
156 - }
157 -
158 - function AsyncText({text, showVersion}) {
159 - const version = readText(text);
160 - const fullText = showVersion ? `${text} [v${version}]` : text;
161 - Scheduler.log(fullText);
162 - return fullText;
163 - }
164 -
165 - function seedNextTextCache(text) {
166 - if (seededCache === null) {
167 - seededCache = createTextCache();
168 - }
169 - seededCache.resolve(text);
170 - }
171 -
172 - function resolveMostRecentTextCache(text) {
173 - if (textCaches.length === 0) {
174 - throw Error('Cache does not exist.');
175 - } else {
176 - // Resolve the most recently created cache. An older cache can by
177 - // resolved with `textCaches[index].resolve(text)`.
178 - textCaches[textCaches.length - 1].resolve(text);
179 - }
180 - }
181 -
182 - // @gate enableCacheElement
183 - test('render Cache component', async () => {
184 - const root = ReactNoop.createRoot();
185 - await act(() => {
186 - root.render(<Cache>Hi</Cache>);
187 - });
188 - expect(root).toMatchRenderedOutput('Hi');
189 - });
190 -
191 - // @gate enableCacheElement
192 - test('mount new data', async () => {
193 - const root = ReactNoop.createRoot();
194 - await act(() => {
195 - root.render(
196 - <Cache>
197 - <Suspense fallback={<Text text="Loading..." />}>
198 - <AsyncText text="A" />
199 - </Suspense>
200 - </Cache>,
201 - );
202 - });
203 - assertLog(['Cache miss! [A]', 'Loading...']);
204 - expect(root).toMatchRenderedOutput('Loading...');
205 -
206 - await act(() => {
207 - resolveMostRecentTextCache('A');
208 - });
209 - assertLog(['A']);
210 - expect(root).toMatchRenderedOutput('A');
211 -
212 - await act(() => {
213 - root.render('Bye');
214 - });
215 - // no cleanup: cache is still retained at the root
216 - assertLog([]);
217 - expect(root).toMatchRenderedOutput('Bye');
218 - });
219 -
220 - // @gate enableCacheElement
221 - test('root acts as implicit cache boundary', async () => {
222 - const root = ReactNoop.createRoot();
223 - await act(() => {
224 - root.render(
225 - <Suspense fallback={<Text text="Loading..." />}>
226 - <AsyncText text="A" />
227 - </Suspense>,
228 - );
229 - });
230 - assertLog(['Cache miss! [A]', 'Loading...']);
231 - expect(root).toMatchRenderedOutput('Loading...');
232 -
233 - await act(() => {
234 - resolveMostRecentTextCache('A');
235 - });
236 - assertLog(['A']);
237 - expect(root).toMatchRenderedOutput('A');
238 -
239 - await act(() => {
240 - root.render('Bye');
241 - });
242 - // no cleanup: cache is still retained at the root
243 - assertLog([]);
244 - expect(root).toMatchRenderedOutput('Bye');
245 - });
246 -
247 - // @gate enableCacheElement
248 - test('multiple new Cache boundaries in the same mount share the same, fresh root cache', async () => {
249 - function App() {
250 - return (
251 - <>
252 - <Cache>
253 - <Suspense fallback={<Text text="Loading..." />}>
254 - <AsyncText text="A" />
255 - </Suspense>
256 - </Cache>
257 - <Cache>
258 - <Suspense fallback={<Text text="Loading..." />}>
259 - <AsyncText text="A" />
260 - </Suspense>
261 - </Cache>
262 - </>
263 - );
264 - }
265 -
266 - const root = ReactNoop.createRoot();
267 - await act(() => {
268 - root.render(<App showMore={false} />);
269 - });
270 -
271 - // Even though there are two new <Cache /> trees, they should share the same
272 - // data cache. So there should be only a single cache miss for A.
273 - assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
274 - expect(root).toMatchRenderedOutput('Loading...Loading...');
275 -
276 - await act(() => {
277 - resolveMostRecentTextCache('A');
278 - });
279 - assertLog(['A', 'A']);
280 - expect(root).toMatchRenderedOutput('AA');
281 -
282 - await act(() => {
283 - root.render('Bye');
284 - });
285 - // no cleanup: cache is still retained at the root
286 - assertLog([]);
287 - expect(root).toMatchRenderedOutput('Bye');
288 - });
289 -
290 - // @gate enableCacheElement
291 - test('multiple new Cache boundaries in the same update share the same, fresh cache', async () => {
292 - function App({showMore}) {
293 - return showMore ? (
294 - <>
295 - <Cache>
296 - <Suspense fallback={<Text text="Loading..." />}>
297 - <AsyncText text="A" />
298 - </Suspense>
299 - </Cache>
300 - <Cache>
301 - <Suspense fallback={<Text text="Loading..." />}>
302 - <AsyncText text="A" />
303 - </Suspense>
304 - </Cache>
305 - </>
306 - ) : (
307 - '(empty)'
308 - );
309 - }
310 -
311 - const root = ReactNoop.createRoot();
312 - await act(() => {
313 - root.render(<App showMore={false} />);
314 - });
315 - assertLog([]);
316 - expect(root).toMatchRenderedOutput('(empty)');
317 -
318 - await act(() => {
319 - root.render(<App showMore={true} />);
320 - });
321 - // Even though there are two new <Cache /> trees, they should share the same
322 - // data cache. So there should be only a single cache miss for A.
323 - assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
324 - expect(root).toMatchRenderedOutput('Loading...Loading...');
325 -
326 - await act(() => {
327 - resolveMostRecentTextCache('A');
328 - });
329 - assertLog(['A', 'A']);
330 - expect(root).toMatchRenderedOutput('AA');
331 -
332 - await act(() => {
333 - root.render('Bye');
334 - });
335 - // cleanup occurs for the cache shared by the inner cache boundaries (which
336 - // are not shared w the root because they were added in an update)
337 - // note that no cache is created for the root since the cache is never accessed
338 - assertLog(['Cache cleanup: A [v1]']);
339 - expect(root).toMatchRenderedOutput('Bye');
340 - });
341 -
342 - // @gate enableCacheElement
343 - test(
344 - 'nested cache boundaries share the same cache as the root during ' +
345 - 'the initial render',
346 - async () => {
347 - function App() {
348 - return (
349 - <Suspense fallback={<Text text="Loading..." />}>
350 - <AsyncText text="A" />
351 - <Cache>
352 - <AsyncText text="A" />
353 - </Cache>
354 - </Suspense>
355 - );
356 - }
357 -
358 - const root = ReactNoop.createRoot();
359 - await act(() => {
360 - root.render(<App />);
361 - });
362 - // Even though there is a nested <Cache /> boundary, it should share the same
363 - // data cache as the root. So there should be only a single cache miss for A.
364 - assertLog(['Cache miss! [A]', 'Loading...']);
365 - expect(root).toMatchRenderedOutput('Loading...');
366 -
367 - await act(() => {
368 - resolveMostRecentTextCache('A');
369 - });
370 - assertLog(['A', 'A']);
371 - expect(root).toMatchRenderedOutput('AA');
372 -
373 - await act(() => {
374 - root.render('Bye');
375 - });
376 - // no cleanup: cache is still retained at the root
377 - assertLog([]);
378 - expect(root).toMatchRenderedOutput('Bye');
379 - },
380 - );
381 -
382 - // @gate enableCacheElement
383 - test('new content inside an existing Cache boundary should re-use already cached data', async () => {
384 - function App({showMore}) {
385 - return (
386 - <Cache>
387 - <Suspense fallback={<Text text="Loading..." />}>
388 - <AsyncText showVersion={true} text="A" />
389 - </Suspense>
390 - {showMore ? (
391 - <Suspense fallback={<Text text="Loading..." />}>
392 - <AsyncText showVersion={true} text="A" />
393 - </Suspense>
394 - ) : null}
395 - </Cache>
396 - );
397 - }
398 -
399 - const root = ReactNoop.createRoot();
400 - await act(() => {
401 - seedNextTextCache('A');
402 - root.render(<App showMore={false} />);
403 - });
404 - assertLog(['A [v1]']);
405 - expect(root).toMatchRenderedOutput('A [v1]');
406 -
407 - // Add a new cache boundary
408 - await act(() => {
409 - root.render(<App showMore={true} />);
410 - });
411 - assertLog([
412 - 'A [v1]',
413 - // New tree should use already cached data
414 - 'A [v1]',
415 - ]);
416 - expect(root).toMatchRenderedOutput('A [v1]A [v1]');
417 -
418 - await act(() => {
419 - root.render('Bye');
420 - });
421 - // no cleanup: cache is still retained at the root
422 - assertLog([]);
423 - expect(root).toMatchRenderedOutput('Bye');
424 - });
425 -
426 - // @gate enableCacheElement
427 - test('a new Cache boundary uses fresh cache', async () => {
428 - // The only difference from the previous test is that the "Show More"
429 - // content is wrapped in a nested <Cache /> boundary
430 - function App({showMore}) {
431 - return (
432 - <Cache>
433 - <Suspense fallback={<Text text="Loading..." />}>
434 - <AsyncText showVersion={true} text="A" />
435 - </Suspense>
436 - {showMore ? (
437 - <Cache>
438 - <Suspense fallback={<Text text="Loading..." />}>
439 - <AsyncText showVersion={true} text="A" />
440 - </Suspense>
441 - </Cache>
442 - ) : null}
443 - </Cache>
444 - );
445 - }
446 -
447 - const root = ReactNoop.createRoot();
448 - await act(() => {
449 - seedNextTextCache('A');
450 - root.render(<App showMore={false} />);
451 - });
452 - assertLog(['A [v1]']);
453 - expect(root).toMatchRenderedOutput('A [v1]');
454 -
455 - // Add a new cache boundary
456 - await act(() => {
457 - root.render(<App showMore={true} />);
458 - });
459 - assertLog([
460 - 'A [v1]',
461 - // New tree should load fresh data.
462 - 'Cache miss! [A]',
463 - 'Loading...',
464 - ]);
465 - expect(root).toMatchRenderedOutput('A [v1]Loading...');
466 - await act(() => {
467 - resolveMostRecentTextCache('A');
468 - });
469 - assertLog(['A [v2]']);
470 - expect(root).toMatchRenderedOutput('A [v1]A [v2]');
471 -
472 - // Replace all the children: this should retain the root Cache instance,
473 - // but cleanup the separate cache instance created for the fresh cache
474 - // boundary
475 - await act(() => {
476 - root.render('Bye!');
477 - });
478 - // Cleanup occurs for the *second* cache instance: the first is still
479 - // referenced by the root
480 - assertLog(['Cache cleanup: A [v2]']);
481 - expect(root).toMatchRenderedOutput('Bye!');
482 - });
483 -
484 - // @gate enableCacheElement
485 - test('inner/outer cache boundaries uses the same cache instance on initial render', async () => {
486 - const root = ReactNoop.createRoot();
487 -
488 - function App() {
489 - return (
490 - <Cache>
491 - <Suspense fallback={<Text text="Loading shell..." />}>
492 - {/* The shell reads A */}
493 - <Shell>
494 - {/* The inner content reads both A and B */}
495 - <Suspense fallback={<Text text="Loading content..." />}>
496 - <Cache>
497 - <Content />
498 - </Cache>
499 - </Suspense>
500 - </Shell>
501 - </Suspense>
502 - </Cache>
503 - );
504 - }
505 -
506 - function Shell({children}) {
507 - readText('A');
508 - return (
509 - <>
510 - <div>
511 - <Text text="Shell" />
512 - </div>
513 - <div>{children}</div>
514 - </>
515 - );
516 - }
517 -
518 - function Content() {
519 - readText('A');
520 - readText('B');
521 - return <Text text="Content" />;
522 - }
523 -
524 - await act(() => {
525 - root.render(<App />);
526 - });
527 - assertLog(['Cache miss! [A]', 'Loading shell...']);
528 - expect(root).toMatchRenderedOutput('Loading shell...');
529 -
530 - await act(() => {
531 - resolveMostRecentTextCache('A');
532 - });
533 - assertLog([
534 - 'Shell',
535 - // There's a cache miss for B, because it hasn't been read yet. But not
536 - // A, because it was cached when we rendered the shell.
537 - 'Cache miss! [B]',
538 - 'Loading content...',
539 - ]);
540 - expect(root).toMatchRenderedOutput(
541 - <>
542 - <div>Shell</div>
543 - <div>Loading content...</div>
544 - </>,
545 - );
546 -
547 - await act(() => {
548 - resolveMostRecentTextCache('B');
549 - });
550 - assertLog(['Content']);
551 - expect(root).toMatchRenderedOutput(
552 - <>
553 - <div>Shell</div>
554 - <div>Content</div>
555 - </>,
556 - );
557 -
558 - await act(() => {
559 - root.render('Bye');
560 - });
561 - // no cleanup: cache is still retained at the root
562 - assertLog([]);
563 - expect(root).toMatchRenderedOutput('Bye');
564 - });
565 -
566 - // @gate enableCacheElement
567 - test('inner/ outer cache boundaries added in the same update use the same cache instance', async () => {
568 - const root = ReactNoop.createRoot();
569 -
570 - function App({showMore}) {
571 - return showMore ? (
572 - <Cache>
573 - <Suspense fallback={<Text text="Loading shell..." />}>
574 - {/* The shell reads A */}
575 - <Shell>
576 - {/* The inner content reads both A and B */}
577 - <Suspense fallback={<Text text="Loading content..." />}>
578 - <Cache>
579 - <Content />
580 - </Cache>
581 - </Suspense>
582 - </Shell>
583 - </Suspense>
584 - </Cache>
585 - ) : (
586 - '(empty)'
587 - );
588 - }
589 -
590 - function Shell({children}) {
591 - readText('A');
592 - return (
593 - <>
594 - <div>
595 - <Text text="Shell" />
596 - </div>
597 - <div>{children}</div>
598 - </>
599 - );
600 - }
601 -
602 - function Content() {
603 - readText('A');
604 - readText('B');
605 - return <Text text="Content" />;
606 - }
607 -
608 - await act(() => {
609 - root.render(<App showMore={false} />);
610 - });
611 - assertLog([]);
612 - expect(root).toMatchRenderedOutput('(empty)');
613 -
614 - await act(() => {
615 - root.render(<App showMore={true} />);
616 - });
617 - assertLog(['Cache miss! [A]', 'Loading shell...']);
618 - expect(root).toMatchRenderedOutput('Loading shell...');
619 -
620 - await act(() => {
621 - resolveMostRecentTextCache('A');
622 - });
623 - assertLog([
624 - 'Shell',
625 - // There's a cache miss for B, because it hasn't been read yet. But not
626 - // A, because it was cached when we rendered the shell.
627 - 'Cache miss! [B]',
628 - 'Loading content...',
629 - ]);
630 - expect(root).toMatchRenderedOutput(
631 - <>
632 - <div>Shell</div>
633 - <div>Loading content...</div>
634 - </>,
635 - );
636 -
637 - await act(() => {
638 - resolveMostRecentTextCache('B');
639 - });
640 - assertLog(['Content']);
641 - expect(root).toMatchRenderedOutput(
642 - <>
643 - <div>Shell</div>
644 - <div>Content</div>
645 - </>,
646 - );
647 -
648 - await act(() => {
649 - root.render('Bye');
650 - });
651 - assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: B [v1]']);
652 - expect(root).toMatchRenderedOutput('Bye');
653 - });
654 -
655 - // @gate enableCacheElement
656 - test('refresh a cache boundary', async () => {
657 - let refresh;
658 - function App() {
659 - refresh = useCacheRefresh();
660 - return <AsyncText showVersion={true} text="A" />;
661 - }
662 -
663 - // Mount initial data
664 - const root = ReactNoop.createRoot();
665 - await act(() => {
666 - root.render(
667 - <Suspense fallback={<Text text="Loading..." />}>
668 - <App />
669 - </Suspense>,
670 - );
671 - });
672 - assertLog(['Cache miss! [A]', 'Loading...']);
673 - expect(root).toMatchRenderedOutput('Loading...');
674 -
675 - await act(() => {
676 - resolveMostRecentTextCache('A');
677 - });
678 - assertLog(['A [v1]']);
679 - expect(root).toMatchRenderedOutput('A [v1]');
680 -
681 - // Refresh for new data.
682 - await act(() => {
683 - startTransition(() => refresh());
684 - });
685 - assertLog(['Cache miss! [A]', 'Loading...']);
686 - expect(root).toMatchRenderedOutput('A [v1]');
687 -
688 - await act(() => {
689 - resolveMostRecentTextCache('A');
690 - });
691 - // Note that the version has updated
692 - if (getCacheSignal) {
693 - assertLog(['A [v2]', 'Cache cleanup: A [v1]']);
694 - } else {
695 - assertLog(['A [v2]']);
696 - }
697 - expect(root).toMatchRenderedOutput('A [v2]');
698 -
699 - await act(() => {
700 - root.render('Bye');
701 - });
702 - expect(root).toMatchRenderedOutput('Bye');
703 - });
704 -
705 - // @gate enableCacheElement
706 - test('refresh the root cache', async () => {
707 - let refresh;
708 - function App() {
709 - refresh = useCacheRefresh();
710 - return <AsyncText showVersion={true} text="A" />;
711 - }
712 -
713 - // Mount initial data
714 - const root = ReactNoop.createRoot();
715 - await act(() => {
716 - root.render(
717 - <Suspense fallback={<Text text="Loading..." />}>
718 - <App />
719 - </Suspense>,
720 - );
721 - });
722 - assertLog(['Cache miss! [A]', 'Loading...']);
723 - expect(root).toMatchRenderedOutput('Loading...');
724 -
725 - await act(() => {
726 - resolveMostRecentTextCache('A');
727 - });
728 - assertLog(['A [v1]']);
729 - expect(root).toMatchRenderedOutput('A [v1]');
730 -
731 - // Refresh for new data.
732 - await act(() => {
733 - startTransition(() => refresh());
734 - });
735 - assertLog(['Cache miss! [A]', 'Loading...']);
736 - expect(root).toMatchRenderedOutput('A [v1]');
737 -
738 - await act(() => {
739 - resolveMostRecentTextCache('A');
740 - });
741 - // Note that the version has updated, and the previous cache is cleared
742 - assertLog(['A [v2]', 'Cache cleanup: A [v1]']);
743 - expect(root).toMatchRenderedOutput('A [v2]');
744 -
745 - await act(() => {
746 - root.render('Bye');
747 - });
748 - // the original root cache already cleaned up when the refresh completed
749 - assertLog([]);
750 - expect(root).toMatchRenderedOutput('Bye');
751 - });
752 -
753 - // @gate enableCacheElement
754 - test('refresh the root cache without a transition', async () => {
755 - let refresh;
756 - function App() {
757 - refresh = useCacheRefresh();
758 - return <AsyncText showVersion={true} text="A" />;
759 - }
760 -
761 - // Mount initial data
762 - const root = ReactNoop.createRoot();
763 - await act(() => {
764 - root.render(
765 - <Suspense fallback={<Text text="Loading..." />}>
766 - <App />
767 - </Suspense>,
768 - );
769 - });
770 - assertLog(['Cache miss! [A]', 'Loading...']);
771 - expect(root).toMatchRenderedOutput('Loading...');
772 -
773 - await act(() => {
774 - resolveMostRecentTextCache('A');
775 - });
776 - assertLog(['A [v1]']);
777 - expect(root).toMatchRenderedOutput('A [v1]');
778 -
779 - // Refresh for new data.
780 - await act(() => {
781 - refresh();
782 - });
783 - assertLog([
784 - 'Cache miss! [A]',
785 - 'Loading...',
786 - // The v1 cache can be cleaned up since everything that references it has
787 - // been replaced by a fallback. When the boundary switches back to visible
788 - // it will use the v2 cache.
789 - 'Cache cleanup: A [v1]',
790 - ]);
791 - expect(root).toMatchRenderedOutput('Loading...');
792 -
793 - await act(() => {
794 - resolveMostRecentTextCache('A');
795 - });
796 - // Note that the version has updated, and the previous cache is cleared
797 - assertLog(['A [v2]']);
798 - expect(root).toMatchRenderedOutput('A [v2]');
799 -
800 - await act(() => {
801 - root.render('Bye');
802 - });
803 - // the original root cache already cleaned up when the refresh completed
804 - assertLog([]);
805 - expect(root).toMatchRenderedOutput('Bye');
806 - });
807 -
808 - // @gate enableCacheElement
809 - test('refresh a cache with seed data', async () => {
810 - let refresh;
811 - function App() {
812 - refresh = useCacheRefresh();
813 - return <AsyncText showVersion={true} text="A" />;
814 - }
815 -
816 - // Mount initial data
817 - const root = ReactNoop.createRoot();
818 - await act(() => {
819 - root.render(
820 - <Cache>
821 - <Suspense fallback={<Text text="Loading..." />}>
822 - <App />
823 - </Suspense>
824 - </Cache>,
825 - );
826 - });
827 - assertLog(['Cache miss! [A]', 'Loading...']);
828 - expect(root).toMatchRenderedOutput('Loading...');
829 -
830 - await act(() => {
831 - resolveMostRecentTextCache('A');
832 - });
833 - assertLog(['A [v1]']);
834 - expect(root).toMatchRenderedOutput('A [v1]');
835 -
836 - // Refresh for new data.
837 - await act(() => {
838 - // Refresh the cache with seeded data, like you would receive from a
839 - // server mutation.
840 - // TODO: Seeding multiple typed textCaches. Should work by calling `refresh`
841 - // multiple times with different key/value pairs
842 - startTransition(() => {
843 - const textCache = createTextCache();
844 - textCache.resolve('A');
845 - startTransition(() => refresh(createTextCache, textCache));
846 - });
847 - });
848 - // The root should re-render without a cache miss.
849 - // The cache is not cleared up yet, since it's still reference by the root
850 - assertLog(['A [v2]']);
851 - expect(root).toMatchRenderedOutput('A [v2]');
852 -
853 - await act(() => {
854 - root.render('Bye');
855 - });
856 - // the refreshed cache boundary is unmounted and cleans up
857 - assertLog(['Cache cleanup: A [v2]']);
858 - expect(root).toMatchRenderedOutput('Bye');
859 - });
860 -
861 - // @gate enableCacheElement
862 - test('refreshing a parent cache also refreshes its children', async () => {
863 - let refreshShell;
864 - function RefreshShell() {
865 - refreshShell = useCacheRefresh();
866 - return null;
867 - }
868 -
869 - function App({showMore}) {
870 - return (
871 - <Cache>
872 - <RefreshShell />
873 - <Suspense fallback={<Text text="Loading..." />}>
874 - <AsyncText showVersion={true} text="A" />
875 - </Suspense>
876 - {showMore ? (
877 - <Cache>
878 - <Suspense fallback={<Text text="Loading..." />}>
879 - <AsyncText showVersion={true} text="A" />
880 - </Suspense>
881 - </Cache>
882 - ) : null}
883 - </Cache>
884 - );
885 - }
886 -
887 - const root = ReactNoop.createRoot();
888 - await act(() => {
889 - seedNextTextCache('A');
890 - root.render(<App showMore={false} />);
891 - });
892 - assertLog(['A [v1]']);
893 - expect(root).toMatchRenderedOutput('A [v1]');
894 -
895 - // Add a new cache boundary
896 - await act(() => {
897 - seedNextTextCache('A');
898 - root.render(<App showMore={true} />);
899 - });
900 - assertLog([
901 - 'A [v1]',
902 - // New tree should load fresh data.
903 - 'A [v2]',
904 - ]);
905 - expect(root).toMatchRenderedOutput('A [v1]A [v2]');
906 -
907 - // Now refresh the shell. This should also cause the "Show More" contents to
908 - // refresh, since its cache is nested inside the outer one.
909 - await act(() => {
910 - startTransition(() => refreshShell());
911 - });
912 - assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
913 - expect(root).toMatchRenderedOutput('A [v1]A [v2]');
914 -
915 - await act(() => {
916 - resolveMostRecentTextCache('A');
917 - });
918 - assertLog([
919 - 'A [v3]',
920 - 'A [v3]',
921 - // once the refresh completes the inner showMore boundary frees its previous
922 - // cache instance, since it is now using the refreshed parent instance.
923 - 'Cache cleanup: A [v2]',
924 - ]);
925 - expect(root).toMatchRenderedOutput('A [v3]A [v3]');
926 -
927 - await act(() => {
928 - root.render('Bye!');
929 - });
930 - // Unmounting children releases the refreshed cache instance only; the root
931 - // still retains the original cache instance used for the first render
932 - assertLog(['Cache cleanup: A [v3]']);
933 - expect(root).toMatchRenderedOutput('Bye!');
934 - });
935 -
936 - // @gate enableCacheElement
937 - test(
938 - 'refreshing a cache boundary does not refresh the other boundaries ' +
939 - 'that mounted at the same time (i.e. the ones that share the same cache)',
940 - async () => {
941 - let refreshFirstBoundary;
942 - function RefreshFirstBoundary() {
943 - refreshFirstBoundary = useCacheRefresh();
944 - return null;
945 - }
946 -
947 - function App({showMore}) {
948 - return showMore ? (
949 - <>
950 - <Cache>
951 - <Suspense fallback={<Text text="Loading..." />}>
952 - <RefreshFirstBoundary />
953 - <AsyncText showVersion={true} text="A" />
954 - </Suspense>
955 - </Cache>
956 - <Cache>
957 - <Suspense fallback={<Text text="Loading..." />}>
958 - <AsyncText showVersion={true} text="A" />
959 - </Suspense>
960 - </Cache>
961 - </>
962 - ) : null;
963 - }
964 -
965 - // First mount the initial shell without the nested boundaries. This is
966 - // necessary for this test because we want the two inner boundaries to be
967 - // treated like sibling providers that happen to share an underlying
968 - // cache, as opposed to consumers of the root-level cache.
969 - const root = ReactNoop.createRoot();
970 - await act(() => {
971 - root.render(<App showMore={false} />);
972 - });
973 -
974 - // Now reveal the boundaries. In a real app this would be a navigation.
975 - await act(() => {
976 - root.render(<App showMore={true} />);
977 - });
978 -
979 - // Even though there are two new <Cache /> trees, they should share the same
980 - // data cache. So there should be only a single cache miss for A.
981 - assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
982 - expect(root).toMatchRenderedOutput('Loading...Loading...');
983 -
984 - await act(() => {
985 - resolveMostRecentTextCache('A');
986 - });
987 - assertLog(['A [v1]', 'A [v1]']);
988 - expect(root).toMatchRenderedOutput('A [v1]A [v1]');
989 -
990 - // Refresh the first boundary. It should not refresh the second boundary,
991 - // even though they previously shared the same underlying cache.
992 - await act(async () => {
993 - await refreshFirstBoundary();
994 - });
995 - assertLog(['Cache miss! [A]', 'Loading...']);
996 -
997 - await act(() => {
998 - resolveMostRecentTextCache('A');
999 - });
1000 - assertLog(['A [v2]']);
1001 - expect(root).toMatchRenderedOutput('A [v2]A [v1]');
1002 -
1003 - // Unmount children: this should clear *both* cache instances:
1004 - // the root doesn't have a cache instance (since it wasn't accessed
1005 - // during the initial render, and all subsequent cache accesses were within
1006 - // a fresh boundary). Therefore this causes cleanup for both the fresh cache
1007 - // instance in the refreshed first boundary and cleanup for the non-refreshed
1008 - // sibling boundary.
1009 - await act(() => {
1010 - root.render('Bye!');
1011 - });
1012 - assertLog(['Cache cleanup: A [v2]', 'Cache cleanup: A [v1]']);
1013 - expect(root).toMatchRenderedOutput('Bye!');
1014 - },
1015 - );
1016 -
1017 - // @gate enableCacheElement
1018 - test(
1019 - 'mount a new Cache boundary in a sibling while simultaneously ' +
1020 - 'resolving a Suspense boundary',
1021 - async () => {
1022 - function App({showMore}) {
1023 - return (
1024 - <>
1025 - {showMore ? (
1026 - <Suspense fallback={<Text text="Loading..." />}>
1027 - <Cache>
1028 - <AsyncText showVersion={true} text="A" />
1029 - </Cache>
1030 - </Suspense>
1031 - ) : null}
1032 - <Suspense fallback={<Text text="Loading..." />}>
1033 - <Cache>
1034 - {' '}
1035 - <AsyncText showVersion={true} text="A" />{' '}
1036 - <AsyncText showVersion={true} text="B" />
1037 - </Cache>
1038 - </Suspense>
1039 - </>
1040 - );
1041 - }
1042 -
1043 - const root = ReactNoop.createRoot();
1044 - await act(() => {
1045 - root.render(<App showMore={false} />);
1046 - });
1047 - assertLog(['Cache miss! [A]', 'Loading...']);
1048 - expect(root).toMatchRenderedOutput('Loading...');
1049 -
1050 - await act(() => {
1051 - // This will resolve the content in the first cache
1052 - resolveMostRecentTextCache('A');
1053 - resolveMostRecentTextCache('B');
1054 - // And mount the second tree, which includes new content
1055 - root.render(<App showMore={true} />);
1056 - });
1057 - assertLog([
1058 - // The new tree should use a fresh cache
1059 - 'Cache miss! [A]',
1060 - 'Loading...',
1061 - // The other tree uses the cached responses. This demonstrates that the
1062 - // requests are not dropped.
1063 - 'A [v1]',
1064 - 'B [v1]',
1065 - ]);
1066 - expect(root).toMatchRenderedOutput('Loading... A [v1] B [v1]');
1067 -
1068 - // Now resolve the second tree
1069 - await act(() => {
1070 - resolveMostRecentTextCache('A');
1071 - });
1072 - assertLog(['A [v2]']);
1073 - expect(root).toMatchRenderedOutput('A [v2] A [v1] B [v1]');
1074 -
1075 - await act(() => {
1076 - root.render('Bye!');
1077 - });
1078 - // Unmounting children releases both cache boundaries, but the original
1079 - // cache instance (used by second boundary) is still referenced by the root.
1080 - // only the second cache instance is freed.
1081 - assertLog(['Cache cleanup: A [v2]']);
1082 - expect(root).toMatchRenderedOutput('Bye!');
1083 - },
1084 - );
1085 -
1086 - // @gate enableCacheElement
1087 - test('cache pool is cleared once transitions that depend on it commit their shell', async () => {
1088 - function Child({text}) {
1089 - return (
1090 - <Cache>
1091 - <AsyncText showVersion={true} text={text} />
1092 - </Cache>
1093 - );
1094 - }
1095 -
1096 - const root = ReactNoop.createRoot();
1097 - await act(() => {
1098 - root.render(
1099 - <Suspense fallback={<Text text="Loading..." />}>(empty)</Suspense>,
1100 - );
1101 - });
1102 - assertLog([]);
1103 - expect(root).toMatchRenderedOutput('(empty)');
1104 -
1105 - await act(() => {
1106 - startTransition(() => {
1107 - root.render(
1108 - <Suspense fallback={<Text text="Loading..." />}>
1109 - <Child text="A" />
1110 - </Suspense>,
1111 - );
1112 - });
1113 - });
1114 - assertLog(['Cache miss! [A]', 'Loading...']);
1115 - expect(root).toMatchRenderedOutput('(empty)');
1116 -
1117 - await act(() => {
1118 - startTransition(() => {
1119 - root.render(
1120 - <Suspense fallback={<Text text="Loading..." />}>
1121 - <Child text="A" />
1122 - <Child text="A" />
1123 - </Suspense>,
1124 - );
1125 - });
1126 - });
1127 - assertLog([
1128 - // No cache miss, because it uses the pooled cache
1129 - 'Loading...',
1130 - ]);
1131 - expect(root).toMatchRenderedOutput('(empty)');
1132 -
1133 - // Resolve the request
1134 - await act(() => {
1135 - resolveMostRecentTextCache('A');
1136 - });
1137 - assertLog(['A [v1]', 'A [v1]']);
1138 - expect(root).toMatchRenderedOutput('A [v1]A [v1]');
1139 -
1140 - // Now do another transition
1141 - await act(() => {
1142 - startTransition(() => {
1143 - root.render(
1144 - <Suspense fallback={<Text text="Loading..." />}>
1145 - <Child text="A" />
1146 - <Child text="A" />
1147 - <Child text="A" />
1148 - </Suspense>,
1149 - );
1150 - });
1151 - });
1152 - assertLog([
1153 - // First two children use the old cache because they already finished
1154 - 'A [v1]',
1155 - 'A [v1]',
1156 - // The new child uses a fresh cache
1157 - 'Cache miss! [A]',
1158 - 'Loading...',
1159 - ]);
1160 - expect(root).toMatchRenderedOutput('A [v1]A [v1]');
1161 -
1162 - await act(() => {
1163 - resolveMostRecentTextCache('A');
1164 - });
1165 - assertLog(['A [v1]', 'A [v1]', 'A [v2]']);
1166 - expect(root).toMatchRenderedOutput('A [v1]A [v1]A [v2]');
1167 -
1168 - // Unmount children: the first text cache instance is created only after the root
1169 - // commits, so both fresh cache instances are released by their cache boundaries,
1170 - // cleaning up v1 (used for the first two children which render together) and
1171 - // v2 (used for the third boundary added later).
1172 - await act(() => {
1173 - root.render('Bye!');
1174 - });
1175 - assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: A [v2]']);
1176 - expect(root).toMatchRenderedOutput('Bye!');
1177 - });
1178 -
1179 - // @gate enableCacheElement
1180 - test('cache pool is not cleared by arbitrary commits', async () => {
1181 - function App() {
1182 - return (
1183 - <>
1184 - <ShowMore />
1185 - <Unrelated />
1186 - </>
1187 - );
1188 - }
1189 -
1190 - let showMore;
1191 - function ShowMore() {
1192 - const [shouldShow, _showMore] = useState(false);
1193 - showMore = () => _showMore(true);
1194 - return (
1195 - <>
1196 - <Suspense fallback={<Text text="Loading..." />}>
1197 - {shouldShow ? (
1198 - <Cache>
1199 - <AsyncText showVersion={true} text="A" />
1200 - </Cache>
1201 - ) : null}
1202 - </Suspense>
1203 - </>
1204 - );
1205 - }
1206 -
1207 - let updateUnrelated;
1208 - function Unrelated() {
1209 - const [count, _updateUnrelated] = useState(0);
1210 - updateUnrelated = _updateUnrelated;
1211 - return <Text text={String(count)} />;
1212 - }
1213 -
1214 - const root = ReactNoop.createRoot();
1215 - await act(() => {
1216 - root.render(<App />);
1217 - });
1218 - assertLog(['0']);
1219 - expect(root).toMatchRenderedOutput('0');
1220 -
1221 - await act(() => {
1222 - startTransition(() => {
1223 - showMore();
1224 - });
1225 - });
1226 - assertLog(['Cache miss! [A]', 'Loading...']);
1227 - expect(root).toMatchRenderedOutput('0');
1228 -
1229 - await act(() => {
1230 - updateUnrelated(1);
1231 - });
1232 - assertLog([
1233 - '1',
1234 -
1235 - // Happens to re-render the fallback. Doesn't need to, but not relevant
1236 - // to this test.
1237 - 'Loading...',
1238 - ]);
1239 - expect(root).toMatchRenderedOutput('1');
1240 -
1241 - await act(() => {
1242 - resolveMostRecentTextCache('A');
1243 - });
1244 - assertLog(['A [v1]']);
1245 - expect(root).toMatchRenderedOutput('A [v1]1');
1246 -
1247 - // Unmount children: the first text cache instance is created only after initial
1248 - // render after calling showMore(). This instance is cleaned up when that boundary
1249 - // is unmounted. Bc root cache instance is never accessed, the inner cache
1250 - // boundary ends up at v1.
1251 - await act(() => {
1252 - root.render('Bye!');
1253 - });
1254 - assertLog(['Cache cleanup: A [v1]']);
1255 - expect(root).toMatchRenderedOutput('Bye!');
1256 - });
1257 -
1258 - // @gate enableCacheElement
1259 - test('cache boundary uses a fresh cache when its key changes', async () => {
1260 - const root = ReactNoop.createRoot();
1261 - seedNextTextCache('A');
1262 - await act(() => {
1263 - root.render(
1264 - <Suspense fallback="Loading...">
1265 - <Cache key="A">
1266 - <AsyncText showVersion={true} text="A" />
1267 - </Cache>
1268 - </Suspense>,
1269 - );
1270 - });
1271 - assertLog(['A [v1]']);
1272 - expect(root).toMatchRenderedOutput('A [v1]');
1273 -
1274 - seedNextTextCache('B');
1275 - await act(() => {
1276 - root.render(
1277 - <Suspense fallback="Loading...">
1278 - <Cache key="B">
1279 - <AsyncText showVersion={true} text="B" />
1280 - </Cache>
1281 - </Suspense>,
1282 - );
1283 - });
1284 - assertLog(['B [v2]']);
1285 - expect(root).toMatchRenderedOutput('B [v2]');
1286 -
1287 - // Unmount children: the fresh cache instance for B cleans up since the cache boundary
1288 - // is the only owner, while the original cache instance (for A) is still retained by
1289 - // the root.
1290 - await act(() => {
1291 - root.render('Bye!');
1292 - });
1293 - assertLog(['Cache cleanup: B [v2]']);
1294 - expect(root).toMatchRenderedOutput('Bye!');
1295 - });
1296 -
1297 - // @gate enableCacheElement
1298 - test('overlapping transitions after an initial mount use the same fresh cache', async () => {
1299 - const root = ReactNoop.createRoot();
1300 - await act(() => {
1301 - root.render(
1302 - <Suspense fallback="Loading...">
1303 - <Cache key="A">
1304 - <AsyncText showVersion={true} text="A" />
1305 - </Cache>
1306 - </Suspense>,
1307 - );
1308 - });
1309 - assertLog(['Cache miss! [A]']);
1310 - expect(root).toMatchRenderedOutput('Loading...');
1311 -
1312 - await act(() => {
1313 - resolveMostRecentTextCache('A');
1314 - });
1315 - assertLog(['A [v1]']);
1316 - expect(root).toMatchRenderedOutput('A [v1]');
1317 -
1318 - // After a mount, subsequent transitions use a fresh cache
1319 - await act(() => {
1320 - startTransition(() => {
1321 - root.render(
1322 - <Suspense fallback="Loading...">
1323 - <Cache key="B">
1324 - <AsyncText showVersion={true} text="B" />
1325 - </Cache>
1326 - </Suspense>,
1327 - );
1328 - });
1329 - });
1330 - assertLog(['Cache miss! [B]']);
1331 - expect(root).toMatchRenderedOutput('A [v1]');
1332 -
1333 - // Update to a different text and with a different key for the cache
1334 - // boundary: this should still use the fresh cache instance created
1335 - // for the earlier transition
1336 - await act(() => {
1337 - startTransition(() => {
1338 - root.render(
1339 - <Suspense fallback="Loading...">
1340 - <Cache key="C">
1341 - <AsyncText showVersion={true} text="C" />
1342 - </Cache>
1343 - </Suspense>,
1344 - );
1345 - });
1346 - });
1347 - assertLog(['Cache miss! [C]']);
1348 - expect(root).toMatchRenderedOutput('A [v1]');
1349 -
1350 - await act(() => {
1351 - resolveMostRecentTextCache('C');
1352 - });
1353 - assertLog(['C [v2]']);
1354 - expect(root).toMatchRenderedOutput('C [v2]');
1355 -
1356 - // Unmount children: the fresh cache used for the updates is freed, while the
1357 - // original cache (with A) is still retained at the root.
1358 - await act(() => {
1359 - root.render('Bye!');
1360 - });
1361 - assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']);
1362 - expect(root).toMatchRenderedOutput('Bye!');
1363 - });
1364 -
1365 - // @gate enableCacheElement
1366 - test('overlapping updates after an initial mount use the same fresh cache', async () => {
1367 - const root = ReactNoop.createRoot();
1368 - await act(() => {
1369 - root.render(
1370 - <Suspense fallback="Loading...">
1371 - <Cache key="A">
1372 - <AsyncText showVersion={true} text="A" />
1373 - </Cache>
1374 - </Suspense>,
1375 - );
1376 - });
1377 - assertLog(['Cache miss! [A]']);
1378 - expect(root).toMatchRenderedOutput('Loading...');
1379 -
1380 - await act(() => {
1381 - resolveMostRecentTextCache('A');
1382 - });
1383 - assertLog(['A [v1]']);
1384 - expect(root).toMatchRenderedOutput('A [v1]');
1385 -
1386 - // After a mount, subsequent updates use a fresh cache
1387 - await act(() => {
1388 - root.render(
1389 - <Suspense fallback="Loading...">
1390 - <Cache key="B">
1391 - <AsyncText showVersion={true} text="B" />
1392 - </Cache>
1393 - </Suspense>,
1394 - );
1395 - });
1396 - assertLog(['Cache miss! [B]']);
1397 - expect(root).toMatchRenderedOutput('Loading...');
1398 -
1399 - // A second update uses the same fresh cache: even though this is a new
1400 - // Cache boundary, the render uses the fresh cache from the pending update.
1401 - await act(() => {
1402 - root.render(
1403 - <Suspense fallback="Loading...">
1404 - <Cache key="C">
1405 - <AsyncText showVersion={true} text="C" />
1406 - </Cache>
1407 - </Suspense>,
1408 - );
1409 - });
1410 - assertLog(['Cache miss! [C]']);
1411 - expect(root).toMatchRenderedOutput('Loading...');
1412 -
1413 - await act(() => {
1414 - resolveMostRecentTextCache('C');
1415 - });
1416 - assertLog(['C [v2]']);
1417 - expect(root).toMatchRenderedOutput('C [v2]');
1418 -
1419 - // Unmount children: the fresh cache used for the updates is freed, while the
1420 - // original cache (with A) is still retained at the root.
1421 - await act(() => {
1422 - root.render('Bye!');
1423 - });
1424 - assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']);
1425 - expect(root).toMatchRenderedOutput('Bye!');
1426 - });
1427 -
1428 - // @gate enableCacheElement
1429 - test('cleans up cache only used in an aborted transition', async () => {
1430 - const root = ReactNoop.createRoot();
1431 - seedNextTextCache('A');
1432 - await act(() => {
1433 - root.render(
1434 - <Suspense fallback="Loading...">
1435 - <Cache key="A">
1436 - <AsyncText showVersion={true} text="A" />
1437 - </Cache>
1438 - </Suspense>,
1439 - );
1440 - });
1441 - assertLog(['A [v1]']);
1442 - expect(root).toMatchRenderedOutput('A [v1]');
1443 -
1444 - // Start a transition from A -> B..., which should create a fresh cache
1445 - // for the new cache boundary (bc of the different key)
1446 - await act(() => {
1447 - startTransition(() => {
1448 - root.render(
1449 - <Suspense fallback="Loading...">
1450 - <Cache key="B">
1451 - <AsyncText showVersion={true} text="B" />
1452 - </Cache>
1453 - </Suspense>,
1454 - );
1455 - });
1456 - });
1457 - assertLog(['Cache miss! [B]']);
1458 - expect(root).toMatchRenderedOutput('A [v1]');
1459 -
1460 - // ...but cancel by transitioning "back" to A (which we never really left)
1461 - await act(() => {
1462 - startTransition(() => {
1463 - root.render(
1464 - <Suspense fallback="Loading...">
1465 - <Cache key="A">
1466 - <AsyncText showVersion={true} text="A" />
1467 - </Cache>
1468 - </Suspense>,
1469 - );
1470 - });
1471 - });
1472 - assertLog(['A [v1]', 'Cache cleanup: B [v2]']);
1473 - expect(root).toMatchRenderedOutput('A [v1]');
1474 -
1475 - // Unmount children: ...
1476 - await act(() => {
1477 - root.render('Bye!');
1478 - });
1479 - assertLog([]);
1480 - expect(root).toMatchRenderedOutput('Bye!');
1481 - });
1482 -
1483 - // @gate enableCacheElement
1484 - test.skip('if a root cache refresh never commits its fresh cache is released', async () => {
1485 - const root = ReactNoop.createRoot();
1486 - let refresh;
1487 - function Example({text}) {
1488 - refresh = useCacheRefresh();
1489 - return <AsyncText showVersion={true} text={text} />;
1490 - }
1491 - seedNextTextCache('A');
1492 - await act(() => {
1493 - root.render(
1494 - <Suspense fallback="Loading...">
1495 - <Example text="A" />
1496 - </Suspense>,
1497 - );
1498 - });
1499 - assertLog(['A [v1]']);
1500 - expect(root).toMatchRenderedOutput('A [v1]');
1501 -
1502 - await act(() => {
1503 - startTransition(() => {
1504 - refresh();
1505 - });
1506 - });
1507 - assertLog(['Cache miss! [A]']);
1508 - expect(root).toMatchRenderedOutput('A [v1]');
1509 -
1510 - await act(() => {
1511 - root.render('Bye!');
1512 - });
1513 - assertLog([
1514 - // TODO: the v1 cache should *not* be cleaned up, it is still retained by the root
1515 - // The following line is presently yielded but should not be:
1516 - // 'Cache cleanup: A [v1]',
1517 -
1518 - // TODO: the v2 cache *should* be cleaned up, it was created for the abandoned refresh
1519 - // The following line is presently not yielded but should be:
1520 - 'Cache cleanup: A [v2]',
1521 - ]);
1522 - expect(root).toMatchRenderedOutput('Bye!');
1523 - });
1524 -
1525 - // @gate enableCacheElement
1526 - test.skip('if a cache boundary refresh never commits its fresh cache is released', async () => {
1527 - const root = ReactNoop.createRoot();
1528 - let refresh;
1529 - function Example({text}) {
1530 - refresh = useCacheRefresh();
1531 - return <AsyncText showVersion={true} text={text} />;
1532 - }
1533 - seedNextTextCache('A');
1534 - await act(() => {
1535 - root.render(
1536 - <Suspense fallback="Loading...">
1537 - <Cache>
1538 - <Example text="A" />
1539 - </Cache>
1540 - </Suspense>,
1541 - );
1542 - });
1543 - assertLog(['A [v1]']);
1544 - expect(root).toMatchRenderedOutput('A [v1]');
1545 -
1546 - await act(() => {
1547 - startTransition(() => {
1548 - refresh();
1549 - });
1550 - });
1551 - assertLog(['Cache miss! [A]']);
1552 - expect(root).toMatchRenderedOutput('A [v1]');
1553 -
1554 - // Unmount the boundary before the refresh can complete
1555 - await act(() => {
1556 - root.render('Bye!');
1557 - });
1558 - assertLog([
1559 - // TODO: the v2 cache *should* be cleaned up, it was created for the abandoned refresh
1560 - // The following line is presently not yielded but should be:
1561 - 'Cache cleanup: A [v2]',
1562 - ]);
1563 - expect(root).toMatchRenderedOutput('Bye!');
1564 - });
1565 -
1566 - // @gate enableActivity
1567 - // @gate enableCache
1568 - test('prerender a new cache boundary inside an Activity tree', async () => {
1569 - function App({prerenderMore}) {
1570 - return (
1571 - <Activity mode="hidden">
1572 - <div>
1573 - {prerenderMore ? (
1574 - <Cache>
1575 - <AsyncText text="More" />
1576 - </Cache>
1577 - ) : null}
1578 - </div>
1579 - </Activity>
1580 - );
1581 - }
1582 -
1583 - const root = ReactNoop.createRoot();
1584 - await act(() => {
1585 - root.render(<App prerenderMore={false} />);
1586 - });
1587 - assertLog([]);
1588 - expect(root).toMatchRenderedOutput(<div hidden={true} />);
1589 -
1590 - seedNextTextCache('More');
1591 - await act(() => {
1592 - root.render(<App prerenderMore={true} />);
1593 - });
1594 - assertLog(['More']);
1595 - expect(root).toMatchRenderedOutput(<div hidden={true}>More</div>);
1596 - });
1597 -});
packages/react/index.classic.fb.js
-1
@@ -29,7 +29,6 @@ export {
29 cache,
30 startTransition,
31 unstable_Activity,
32 - unstable_Cache,
32 unstable_TracingMarker,
33 unstable_DebugTracingMode,
34 unstable_LegacyHidden,
packages/react/index.experimental.js
-1
@@ -28,7 +28,6 @@ export {
28 memo,
29 cache,
30 startTransition,
31 - unstable_Cache,
31 unstable_DebugTracingMode,
32 unstable_Activity,
33 unstable_postpone,
packages/react/index.js
-1
@@ -49,7 +49,6 @@ export {
49 memo,
50 cache,
51 startTransition,
52 - unstable_Cache,
52 unstable_DebugTracingMode,
53 unstable_LegacyHidden,
54 unstable_Activity,
packages/react/index.modern.fb.js
-1
@@ -28,7 +28,6 @@ export {
28 memo,
29 cache,
30 startTransition,
31 - unstable_Cache,
31 unstable_DebugTracingMode,
32 unstable_LegacyHidden,
33 unstable_Activity,
packages/react/src/ReactClient.js
-2
@@ -18,7 +18,6 @@ import {
18 REACT_LEGACY_HIDDEN_TYPE,
19 REACT_OFFSCREEN_TYPE,
20 REACT_SCOPE_TYPE,
21 - REACT_CACHE_TYPE,
21 REACT_TRACING_MARKER_TYPE,
22 } from 'shared/ReactSymbols';
23
@@ -119,7 +118,6 @@ export {
118 getCacheSignal as unstable_getCacheSignal,
119 getCacheForType as unstable_getCacheForType,
120 useCacheRefresh as unstable_useCacheRefresh,
122 - REACT_CACHE_TYPE as unstable_Cache,
121 use,
122 useMemoCache as unstable_useMemoCache,
123 // enableScopeAPI
packages/shared/ReactFeatureFlags.js
-1
@@ -75,7 +75,6 @@ export const enableLegacyFBSupport = false;
75
76 export const enableCache = true;
77 export const enableLegacyCache = __EXPERIMENTAL__;
78 -export const enableCacheElement = __EXPERIMENTAL__;
78 export const enableFetchInstrumentation = true;
79
80 export const enableBinaryFlight = __EXPERIMENTAL__;
packages/shared/ReactSymbols.js
-1
@@ -35,7 +35,6 @@ export const REACT_OFFSCREEN_TYPE: symbol = Symbol.for('react.offscreen');
35 export const REACT_LEGACY_HIDDEN_TYPE: symbol = Symbol.for(
36 'react.legacy_hidden',
37 );
38 -export const REACT_CACHE_TYPE: symbol = Symbol.for('react.cache');
38 export const REACT_TRACING_MARKER_TYPE: symbol = Symbol.for(
39 'react.tracing_marker',
40 );
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -43,7 +43,6 @@ export const enableProfilerNestedUpdatePhase = __PROFILE__;
43 export const enableUpdaterTracking = __PROFILE__;
44 export const enableCache = true;
45 export const enableLegacyCache = false;
46 -export const enableCacheElement = true;
46 export const enableFetchInstrumentation = false;
47 export const enableBinaryFlight = true;
48 export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -48,7 +48,6 @@ export const enableRenderableContext = __TODO_NEXT_RN_MAJOR__;
48 const __NEXT_RN_MAJOR__ = true;
49 export const disableClientCache = __NEXT_RN_MAJOR__;
50 export const disableLegacyContext = __NEXT_RN_MAJOR__;
51 -export const enableCacheElement = __NEXT_RN_MAJOR__;
51 export const enableTaint = __NEXT_RN_MAJOR__;
52 export const enableUnifiedSyncLane = __NEXT_RN_MAJOR__;
53 export const enableFizzExternalRuntime = __NEXT_RN_MAJOR__; // DOM-only
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -20,7 +20,6 @@ export const enableProfilerNestedUpdatePhase = __PROFILE__;
20 export const enableUpdaterTracking = false;
21 export const enableCache = true;
22 export const enableLegacyCache = __EXPERIMENTAL__;
23 -export const enableCacheElement = __EXPERIMENTAL__;
23 export const enableFetchInstrumentation = true;
24 export const enableBinaryFlight = true;
25 export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
-1
@@ -20,7 +20,6 @@ export const enableProfilerNestedUpdatePhase = __PROFILE__;
20 export const enableUpdaterTracking = false;
21 export const enableCache = true;
22 export const enableLegacyCache = false;
23 -export const enableCacheElement = true;
23 export const enableFetchInstrumentation = false;
24 export const enableBinaryFlight = true;
25 export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -20,7 +20,6 @@ export const enableProfilerNestedUpdatePhase = __PROFILE__;
20 export const enableUpdaterTracking = false;
21 export const enableCache = true;
22 export const enableLegacyCache = true;
23 -export const enableCacheElement = true;
23 export const enableFetchInstrumentation = false;
24 export const enableBinaryFlight = true;
25 export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.www.js
-1
@@ -67,7 +67,6 @@ export const enableGetInspectorDataForInstanceInProduction = false;
67
68 export const enableCache = true;
69 export const enableLegacyCache = true;
70 -export const enableCacheElement = true;
70 export const enableFetchInstrumentation = false;
71
72 export const enableBinaryFlight = false;
packages/shared/getComponentNameFromType.js
-6
@@ -23,13 +23,11 @@ import {
23 REACT_SUSPENSE_TYPE,
24 REACT_SUSPENSE_LIST_TYPE,
25 REACT_LAZY_TYPE,
26 - REACT_CACHE_TYPE,
26 REACT_TRACING_MARKER_TYPE,
27 } from 'shared/ReactSymbols';
28
29 import {
30 enableTransitionTracing,
32 - enableCache,
31 enableRenderableContext,
32 } from './ReactFeatureFlags';
33
@@ -83,10 +81,6 @@ export default function getComponentNameFromType(type: mixed): string | null {
81 return 'Suspense';
82 case REACT_SUSPENSE_LIST_TYPE:
83 return 'SuspenseList';
86 - case REACT_CACHE_TYPE:
87 - if (enableCache) {
88 - return 'Cache';
89 - }
84 // Fall through
85 case REACT_TRACING_MARKER_TYPE:
86 if (enableTransitionTracing) {
packages/shared/isValidElementType.js
-3
@@ -23,12 +23,10 @@ import {
23 REACT_SCOPE_TYPE,
24 REACT_LEGACY_HIDDEN_TYPE,
25 REACT_OFFSCREEN_TYPE,
26 - REACT_CACHE_TYPE,
26 REACT_TRACING_MARKER_TYPE,
27 } from 'shared/ReactSymbols';
28 import {
29 enableScopeAPI,
31 - enableCacheElement,
30 enableTransitionTracing,
31 enableDebugTracing,
32 enableLegacyHidden,
@@ -53,7 +51,6 @@ export default function isValidElementType(type: mixed): boolean {
51 (enableLegacyHidden && type === REACT_LEGACY_HIDDEN_TYPE) ||
52 type === REACT_OFFSCREEN_TYPE ||
53 (enableScopeAPI && type === REACT_SCOPE_TYPE) ||
56 - (enableCacheElement && type === REACT_CACHE_TYPE) ||
54 (enableTransitionTracing && type === REACT_TRACING_MARKER_TYPE)
55 ) {
56 return true;