Skip to content

Commit 5c60736

Browse files
authored
Remove client caching from cache() API (#27977)
We haven't yet decided how we want `cache` to work on the client. The lifetime of the cache is more complex than on the server, where it only has to live as long as a single request. Since it's more important to ship this on the server, we're removing the existing behavior from the client for now. On the client (i.e. not a Server Components environment) `cache` will have not have any caching behavior. `cache(fn)` will return the function as-is. We intend to implement client caching in a future major release. In the meantime, it's only exposed as an API so that Shared Components can use per-request caching on the server without breaking on the client.
1 parent f16344e commit 5c60736

File tree

8 files changed

+1810
-1712
lines changed

8 files changed

+1810
-1712
lines changed

packages/react-reconciler/src/__tests__/ReactCache-test.js

Lines changed: 153 additions & 1688 deletions
Large diffs are not rendered by default.

packages/react-reconciler/src/__tests__/ReactCacheElement-test.js

Lines changed: 1597 additions & 0 deletions
Large diffs are not rendered by default.

packages/react-reconciler/src/__tests__/ReactUse-test.js

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ let useMemo;
1111
let useEffect;
1212
let Suspense;
1313
let startTransition;
14-
let cache;
1514
let pendingTextRequests;
1615
let waitFor;
1716
let waitForPaint;
@@ -34,7 +33,6 @@ describe('ReactUse', () => {
3433
useEffect = React.useEffect;
3534
Suspense = React.Suspense;
3635
startTransition = React.startTransition;
37-
cache = React.cache;
3836

3937
const InternalTestUtils = require('internal-test-utils');
4038
waitForAll = InternalTestUtils.waitForAll;
@@ -643,10 +641,10 @@ describe('ReactUse', () => {
643641
});
644642

645643
test('when waiting for data to resolve, an update on a different root does not cause work to be dropped', async () => {
646-
const getCachedAsyncText = cache(getAsyncText);
644+
const promise = getAsyncText('Hi');
647645

648646
function App() {
649-
return <Text text={use(getCachedAsyncText('Hi'))} />;
647+
return <Text text={use(promise)} />;
650648
}
651649

652650
const root1 = ReactNoop.createRoot();
@@ -998,39 +996,46 @@ describe('ReactUse', () => {
998996
);
999997

1000998
test('load multiple nested Suspense boundaries', async () => {
1001-
const getCachedAsyncText = cache(getAsyncText);
999+
const promiseA = getAsyncText('A');
1000+
const promiseB = getAsyncText('B');
1001+
const promiseC = getAsyncText('C');
1002+
assertLog([
1003+
'Async text requested [A]',
1004+
'Async text requested [B]',
1005+
'Async text requested [C]',
1006+
]);
10021007

1003-
function AsyncText({text}) {
1004-
return <Text text={use(getCachedAsyncText(text))} />;
1008+
function AsyncText({promise}) {
1009+
return <Text text={use(promise)} />;
10051010
}
10061011

10071012
const root = ReactNoop.createRoot();
10081013
await act(() => {
10091014
root.render(
10101015
<Suspense fallback={<Text text="(Loading A...)" />}>
1011-
<AsyncText text="A" />
1016+
<AsyncText promise={promiseA} />
10121017
<Suspense fallback={<Text text="(Loading B...)" />}>
1013-
<AsyncText text="B" />
1018+
<AsyncText promise={promiseB} />
10141019
<Suspense fallback={<Text text="(Loading C...)" />}>
1015-
<AsyncText text="C" />
1020+
<AsyncText promise={promiseC} />
10161021
</Suspense>
10171022
</Suspense>
10181023
</Suspense>,
10191024
);
10201025
});
1021-
assertLog(['Async text requested [A]', '(Loading A...)']);
1026+
assertLog(['(Loading A...)']);
10221027
expect(root).toMatchRenderedOutput('(Loading A...)');
10231028

10241029
await act(() => {
10251030
resolveTextRequests('A');
10261031
});
1027-
assertLog(['A', 'Async text requested [B]', '(Loading B...)']);
1032+
assertLog(['A', '(Loading B...)']);
10281033
expect(root).toMatchRenderedOutput('A(Loading B...)');
10291034

10301035
await act(() => {
10311036
resolveTextRequests('B');
10321037
});
1033-
assertLog(['B', 'Async text requested [C]', '(Loading C...)']);
1038+
assertLog(['B', '(Loading C...)']);
10341039
expect(root).toMatchRenderedOutput('AB(Loading C...)');
10351040

10361041
await act(() => {
@@ -1584,34 +1589,38 @@ describe('ReactUse', () => {
15841589
});
15851590

15861591
test('regression test: updates while component is suspended should not be mistaken for render phase updates', async () => {
1587-
const getCachedAsyncText = cache(getAsyncText);
1592+
const promiseA = getAsyncText('A');
1593+
const promiseB = getAsyncText('B');
1594+
const promiseC = getAsyncText('C');
1595+
assertLog([
1596+
'Async text requested [A]',
1597+
'Async text requested [B]',
1598+
'Async text requested [C]',
1599+
]);
15881600

15891601
let setState;
15901602
function App() {
1591-
const [state, _setState] = useState('A');
1603+
const [state, _setState] = useState(promiseA);
15921604
setState = _setState;
1593-
return <Text text={use(getCachedAsyncText(state))} />;
1605+
return <Text text={use(state)} />;
15941606
}
15951607

15961608
// Initial render
15971609
const root = ReactNoop.createRoot();
15981610
await act(() => root.render(<App />));
1599-
assertLog(['Async text requested [A]']);
16001611
expect(root).toMatchRenderedOutput(null);
16011612
await act(() => resolveTextRequests('A'));
16021613
assertLog(['A']);
16031614
expect(root).toMatchRenderedOutput('A');
16041615

16051616
// Update to B. This will suspend.
1606-
await act(() => startTransition(() => setState('B')));
1607-
assertLog(['Async text requested [B]']);
1617+
await act(() => startTransition(() => setState(promiseB)));
16081618
expect(root).toMatchRenderedOutput('A');
16091619

16101620
// While B is suspended, update to C. This should immediately interrupt
16111621
// the render for B. In the regression, this update was mistakenly treated
16121622
// as a render phase update.
1613-
ReactNoop.flushSync(() => setState('C'));
1614-
assertLog(['Async text requested [C]']);
1623+
ReactNoop.flushSync(() => setState(promiseC));
16151624

16161625
// Finish rendering.
16171626
await act(() => resolveTextRequests('C'));
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export function cache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T {
11+
// On the client (i.e. not a Server Components environment) `cache` has
12+
// no caching behavior. We just return the function as-is.
13+
//
14+
// We intend to implement client caching in a future major release. In the
15+
// meantime, it's only exposed as an API so that Shared Components can use
16+
// per-request caching on the server without breaking on the client. But it
17+
// does mean they need to be aware of the behavioral difference.
18+
//
19+
// The rest of the behavior is the same as the server implementation — it
20+
// returns a new reference, extra properties like `displayName` are not
21+
// preserved, the length of the new function is 0, etc. That way apps can't
22+
// accidentally depend on those details.
23+
return function () {
24+
// $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code.
25+
return fn.apply(null, arguments);
26+
};
27+
}

packages/react/src/ReactClient.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {createContext} from './ReactContext';
3535
import {lazy} from './ReactLazy';
3636
import {forwardRef} from './ReactForwardRef';
3737
import {memo} from './ReactMemo';
38-
import {cache} from './ReactCache';
38+
import {cache} from './ReactCacheClient';
3939
import {postpone} from './ReactPostpone';
4040
import {
4141
getCacheSignal,

packages/react/src/ReactServer.experimental.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
import {forwardRef} from './ReactForwardRef';
3939
import {lazy} from './ReactLazy';
4040
import {memo} from './ReactMemo';
41-
import {cache} from './ReactCache';
41+
import {cache} from './ReactCacheServer';
4242
import {startTransition} from './ReactStartTransition';
4343
import {postpone} from './ReactPostpone';
4444
import version from 'shared/ReactVersion';

packages/react/src/ReactServer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
import {forwardRef} from './ReactForwardRef';
3636
import {lazy} from './ReactLazy';
3737
import {memo} from './ReactMemo';
38-
import {cache} from './ReactCache';
38+
import {cache} from './ReactCacheServer';
3939
import {startTransition} from './ReactStartTransition';
4040
import version from 'shared/ReactVersion';
4141

0 commit comments

Comments
 (0)