cache
cache is only for use with React Server Components.
cache lets you cache the result of a data fetch or computation.
const cachedFn = cache(fn);Reference {/reference/}
cache(fn) {/cache/}
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 {/parameters/}
fn: The function you want to cache results for.fncan take any arguments and return any value.
Returns {/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.
The optimization of caching return values based on inputs is known as memoization. We refer to the function returned from cache as a memoized function.
Caveats {/caveats/}
- React will invalidate the cache for all memoized functions for each server request.
- Each call to
cachecreates a new function. This means that callingcachewith the same function multiple times will return different memoized functions that do not share the same cache. cachedFnwill also cache errors. Iffnthrows an error for certain arguments, it will be cached, and the same error is re-thrown whencachedFnis called with those same arguments.cacheis for use in Server Components only.
Usage {/usage/}
Cache an expensive computation {/cache-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 getUserMetricsgetUserMetrics 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
Calling different memoized functions will read from different caches. {/pitfall-different-memoized-functions/}
To access the same cache, components must call the same memoized function.
import {cache} from 'react';import {calculateWeekReport} from './report';
export function Temperature({cityData}) { // 🚩 Wrong: Calling `cache` in component creates new `getWeekReport` for each render const getWeekReport = cache(calculateWeekReport); const report = getWeekReport(cityData); // ...}import {cache} from 'react';import {calculateWeekReport} from './report';
// 🚩 Wrong: `getWeekReport` is only accessible for `Precipitation` component.const getWeekReport = cache(calculateWeekReport);
export function Precipitation({cityData}) { const report = getWeekReport(cityData); // ...}In the above example, PrecipitationTemperaturecache to create a new memoized function with their own cache look-up. If both components render for the same cityData, they will do duplicate work to call calculateWeekReport.
In addition, Temperature creates a
To maximize cache hits and reduce work, the two components should call the same memoized function to access the same cache. Instead, define the memoized function in a dedicated module that can be import-ed across components.
import {cache} from 'react';import {calculateWeekReport} from './report';
export default cache(calculateWeekReport);import getWeekReport from './getWeekReport';
export default function Temperature({cityData}) { const report = getWeekReport(cityData); // ...}import getWeekReport from './getWeekReport';
export default function Precipitation({cityData}) { const report = getWeekReport(cityData); // ...}Here, both components call the ./getWeekReport.js to read and write to the same cache.
Share a snapshot of data {/take-and-share-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
If AnimatedWeatherCard and MinimalWeatherCard supply different getTemperaturefetchTemperature will be called twice and each call site will receive different data.
The
async function AnimatedWeatherCard({city}) { const temperature = await getTemperature(city); // ...}Preload data {/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 getUsergetUserPage is doing other computational work and rendering children.
When rendering Profile, we call getUsergetUserProfile
Caching asynchronous work {/caching-asynchronous-work/}
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 fetchDatafetch.
async function fetchData() { return await fetch(`https://...`);}
const getData = cache(fetchData);
async function MyComponent() { getData(); // ... some computational work await getData(); // ...}In calling getDatafetchData
Notice that the first getDataawait whereas the await is a JavaScript operator that will wait and return the settled result of the promise. The first getDatafetch to cache the promise for the second getData
If by the 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
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.
Calling a memoized function outside of a component will not use the cache. {/pitfall-memoized-call-outside-component/}
import {cache} from 'react';
const getUser = cache(async (userId) => { return await db.user.query(userId);});
// 🚩 Wrong: Calling memoized function outside of component will not memoize.getUser('demo-id');
async function DemoProfile() { // ✅ Good: `getUser` will memoize. const user = await getUser('demo-id'); return <Profile user={user} />;}React only provides cache access to the memoized function in a component. When calling getUser
This is because cache access is provided through a context which is only accessible from a component.
When should I use cache, memo, or useMemo? {/cache-memo-usememo/}
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 {/deep-dive-use-memo/}
In general, you should use useMemo for caching a 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 WeatherReports 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 {/deep-dive-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 WeatherReportWeatherReportcache is also recommended for 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 {/deep-dive-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 {/troubleshooting/}
My memoized function still runs even though I’ve called it with the same arguments {/memoized-function-still-runs/}
See prior mentioned pitfalls
- Calling different memoized functions will read from different caches.
- Calling a memoized function outside of a component will not use the cache.
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} /> </> );}In this case the two MapMarkers look like they’re doing the same work and calling calculateNorm with the same value of {x: 10, y: 10, z:10}. Even though the objects contain the same values, they are not the same object reference as each component creates its own props object.
React will call Object.is on the input to verify if there is a cache hit.
import {cache} from 'react';
const calculateNorm = cache((x, y, z) => { // ...});
function MapMarker(props) { // ✅ Good: Pass primitives to memoized function const length = calculateNorm(props.x, props.y, props.z); // ...}
function App() { return ( <> <MapMarker x={10} y={10} z={10} /> <MapMarker x={10} y={10} z={10} /> </> );}One way to address this could be to pass the vector dimensions to calculateNorm. This works because the dimensions themselves are primitives.
Another solution may be to pass the vector object itself as a prop to the component. We’ll need to pass the same object to both component instances.
import {cache} from 'react';
const calculateNorm = cache((vector) => { // ...});
function MapMarker(props) { // ✅ Good: Pass the same `vector` object const length = calculateNorm(props.vector); // ...}
function App() { const vector = [10, 10, 10]; return ( <> <MapMarker vector={vector} /> <MapMarker vector={vector} /> </> );}