cache
cache
lets you cache the result of a data fetch or computation.
const cachedFn = cache(fn);
Reference
cache(fn)
Call
cache
outside of any components to create a version of the function with caching.
import {cache} from 'react';
import calculateMetrics from 'lib/metrics';
const getMetrics = cache(calculateMetrics);
function Chart({data}) {
const report = getMetrics(data);
// ...
}
When
getMetrics
is first called with
data
,
getMetrics
will call
calculateMetrics(data)
and store the result in cache. If
getMetrics
is called again with the same
data
, it will return the cached result instead of calling
calculateMetrics(data)
again.
Parameters
fn
: The function you want to cache results for.
fn
can take any arguments and return any value.
Returns
cache
returns a cached version of
fn
with the same type signature. It does not call
fn
in the process.
When calling
cachedFn
with given arguments, it first checks if a cached result exists in the cache. If a cached result exists, it returns the result. If not, it calls
fn
with the arguments, stores the result in the cache, and returns the result. The only time
fn
is called is when there is a cache miss.
Caveats
cache
creates a new function. This means that calling
cache
with the same function multiple times will return different memoized functions that do not share the same cache.
cachedFn
will also cache errors. If
fn
throws an error for certain arguments, it will be cached, and the same error is re-thrown when
cachedFn
is called with those same arguments.
cache
is for use in
Server Components
only.
Usage
Cache an expensive computation
Use
cache
to skip duplicate work.
import {cache} from 'react';
import calculateUserMetrics from 'lib/user';
const getUserMetrics = cache(calculateUserMetrics);
function Profile({user}) {
const metrics = getUserMetrics(user);
// ...
}
function TeamReport({users}) {
for (let user in users) {
const metrics = getUserMetrics(user);
// ...
}
// ...
}
If the same
user
object is rendered in both
Profile
and
TeamReport
, the two components can share work and only call
calculateUserMetrics
once for that
user
.
Assume
Profile
is rendered first. It will call
getUserMetrics
, and check if there is a cached result. Since it is the first time
getUserMetrics
is called with that
user
, there will be a cache miss.
getUserMetrics
will then call
calculateUserMetrics
with that
user
and write the result to cache.
When
TeamReport
renders its list of
users
and reaches the same
user
object, it will call
getUserMetrics
and read the result from cache.
If
calculateUserMetrics
can be aborted by passing an
AbortSignal
, you can use
cacheSignal()
to cancel the expensive computation if React has finished rendering.
calculateUserMetrics
may already handle cancellation internally by using
cacheSignal
directly.
Share a snapshot of data
To share a snapshot of data between components, call
cache
with a data-fetching function like
fetch
. When multiple components make the same data fetch, only one request is made and the data returned is cached and shared across components. All components refer to the same snapshot of data across the server render.
import {cache} from 'react';
import {fetchTemperature} from './api.js';
const getTemperature = cache(async (city) => {
return await fetchTemperature(city);
});
async function AnimatedWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}
async function MinimalWeatherCard({city}) {
const temperature = await getTemperature(city);
// ...
}
If
AnimatedWeatherCard
and
MinimalWeatherCard
both render for the same
city
, they will receive the same snapshot of data from the
memoized function
.
If
AnimatedWeatherCard
and
MinimalWeatherCard
supply different
city
arguments to
getTemperature
, then
fetchTemperature
will be called twice and each call site will receive different data.
The city acts as a cache key.
Preload data
By caching a long-running data fetch, you can kick off asynchronous work prior to rendering the component.
const getUser = cache(async (id) => {
return await db.user.query(id);
});
async function Profile({id}) {
const user = await getUser(id);
return (
<section>
<img src={user.profilePic} />
<h2>{user.name}</h2>
</section>
);
}
function Page({id}) {
// ✅ Good: start fetching the user data
getUser(id);
// ... some computational work
return (
<>
<Profile id={id} />
</>
);
}
When rendering
Page
, the component calls
getUser
but note that it doesn’t use the returned data. This early
getUser
call kicks off the asynchronous database query that occurs while
Page
is doing other computational work and rendering children.
When rendering
Profile
, we call
getUser
again. If the initial
getUser
call has already returned and cached the user data, when
Profile
asks and waits for this data
, it can simply read from the cache without requiring another remote procedure call. If the
initial data request
hasn’t been completed, preloading data in this pattern reduces delay in data-fetching.
Deep Dive
When evaluating an asynchronous function , you will receive a Promise for that work. The promise holds the state of that work ( pending , fulfilled , failed ) and its eventual settled result.
In this example, the asynchronous function
fetchData
returns a promise that is awaiting the
fetch
.
async function fetchData() {
return await fetch(`https://...`);
}
const getData = cache(fetchData);
async function MyComponent() {
getData();
// ... some computational work
await getData();
// ...
}
In calling
getData
the first time, the promise returned from
fetchData
is cached. Subsequent look-ups will then return the same promise.
Notice that the first
getData
call does not
await
whereas the
second
does.
await
is a JavaScript operator that will wait and return the settled result of the promise. The first
getData
call simply initiates the
fetch
to cache the promise for the second
getData
to look-up.
If by the
second call
the promise is still
pending
, then
await
will pause for the result. The optimization is that while we wait on the
fetch
, React can continue with computational work, thus reducing the wait time for the
second call
.
If the promise is already settled, either to an error or the
fulfilled
result,
await
will return that value immediately. In both outcomes, there is a performance benefit.
Deep Dive
All mentioned APIs offer memoization but the difference is what they’re intended to memoize, who can access the cache, and when their cache is invalidated.
useMemo
In general, you should use
useMemo
for caching an expensive computation in a Client Component across renders. As an example, to memoize a transformation of data within a component.
'use client';
function WeatherReport({record}) {
const avgTemp = useMemo(() => calculateAvg(record), record);
// ...
}
function App() {
const record = getRecord();
return (
<>
<WeatherReport record={record} />
<WeatherReport record={record} />
</>
);
}
In this example,
App
renders two
WeatherReport
s with the same record. Even though both components do the same work, they cannot share work.
useMemo
’s cache is only local to the component.
However,
useMemo
does ensure that if
App
re-renders and the
record
object doesn’t change, each component instance would skip work and use the memoized value of
avgTemp
.
useMemo
will only cache the last computation of
avgTemp
with the given dependencies.
cache
In general, you should use
cache
in Server Components to memoize work that can be shared across components.
const cachedFetchReport = cache(fetchReport);
function WeatherReport({city}) {
const report = cachedFetchReport(city);
// ...
}
function App() {
const city = "Los Angeles";
return (
<>
<WeatherReport city={city} />
<WeatherReport city={city} />
</
>
);
}
Re-writing the previous example to use
cache
, in this case the
second instance of
WeatherReport
will be able to skip duplicate work and read from the same cache as the
first
WeatherReport
. Another difference from the previous example is that
cache
is also recommended for
memoizing data fetches
, unlike
useMemo
which should only be used for computations.
At this time,
cache
should only be used in Server Components and the cache will be invalidated across server requests.
memo
You should use
memo
to prevent a component re-rendering if its props are unchanged.
'use client';
function WeatherReport({record}) {
const avgTemp = calculateAvg(record);
// ...
}
const MemoWeatherReport = memo(WeatherReport);
function App() {
const record = getRecord();
return (
<>
<MemoWeatherReport record={record} />
<MemoWeatherReport record={record} />
</>
);
}
In this example, both
MemoWeatherReport
components will call
calculateAvg
when first rendered. However, if
App
re-renders, with no changes to
record
, none of the props have changed and
MemoWeatherReport
will not re-render.
Compared to
useMemo
,
memo
memoizes the component render based on props vs. specific computations. Similar to
useMemo
, the memoized component only caches the last render with the last prop values. Once the props change, the cache invalidates and the component re-renders.
Troubleshooting
My memoized function still runs even though I’ve called it with the same arguments
See prior mentioned pitfalls
If none of the above apply, it may be a problem with how React checks if something exists in cache.
If your arguments are not primitives (ex. objects, functions, arrays), ensure you’re passing the same object reference.
When calling a memoized function, React will look up the input arguments to see if a result is already cached. React will use shallow equality of the arguments to determine if there is a cache hit.
import {cache} from 'react';
const calculateNorm = cache((vector) => {
// ...
});
function MapMarker(props) {
// 🚩 Wrong: props is an object that changes every render.
const length = calculateNorm(props);
// ...
}
function App() {
return (
<>
<MapMarker x={10} y={10} z={10} />
<MapMarker x={10} y={10} z={10} />
</>
);
}