cache

Caches a function's result per server request so repeated calls with the same arguments run once.

Signature
const cachedFn = cache(fn)
  • Added in React 19.0.0

Parameters

ParameterTypeDescription
$fn(...args: any[]) => anyThe function you want to cache results for. fn can take any arguments and return any value.

Return value

FunctionA cached version of fn with the same type signature. cache does not call fn in the process. When cachedFn is called, it checks whether a result for those arguments is already cached and returns it if so; otherwise it calls fn, stores the result and returns it. fn runs only on a cache miss.

Errors

`cachedFn` caches errors too: if `fn` throws for a set of arguments, the same error is re-thrown for those arguments for the rest of the request.

Changelog

  • PHP 19.0.0Shipped as a stable Server Components API. Client caching was removed from `cache()` during the 19 cycle — it is now for Server Components only.

cache memoizes a function for the duration of one server request. Call the cached function from five different Server Components with the same arguments and the underlying work happens once; React invalidates the whole cache for every new request, so no data leaks between users.

When to reach for it

It replaces prop-drilling data through a server tree. A layout, a header and a sidebar can all call getUser(id) independently, stay decoupled, and still issue a single query. It is also how you preload: call the cached function early without awaiting it, then await it deeper down where the value is actually needed — the request has been in flight the whole time.

Identity is the cache key

Each cache call creates a new memoized function with its own cache. Wrapping the same function twice in two modules gives you two independent caches that never share a hit, so export one cached instance from one module and import it everywhere. Arguments are compared by identity, which means a fresh object literal per call is always a miss — pass primitives, or a stable object reference.

Where it does not apply

cache is for Server Components only. In a Client Component, memoize with useMemo, or hold the promise in your data layer.

Example

Example · jsx
// app/data.js
import { cache } from "react";
import { db } from "./db";

// One cached instance, exported once and imported everywhere.
export const getUser = cache(async id => {
  console.log("querying user", id); // logs once per request
  return db.user.findUnique({ where: { id } });
});

// app/profile/page.jsx — Server Components
import { getUser } from "../data.js";

async function Avatar({ id }) {
  const user = await getUser(id);
  return <img src={user.avatarUrl} alt="" width={48} height={48} />;
}

async function UserName({ id }) {
  const user = await getUser(id); // cache hit: no second query
  return <h1>{user.name}</h1>;
}

export default async function ProfilePage() {
  const id = "user_1";
  return (
    <header>
      <Avatar id={id} />
      <UserName id={id} />
    </header>
  );
}

When to use it

  • Let a layout, a header and a page each call getUser(id) while the database is queried once per request.
  • Kick off a slow query at the top of a route without awaiting it, then await the same cached call deeper in the tree.
  • Deduplicate an expensive permissions check that several unrelated server components all need.

Frequently asked questions

Why does my cached function still run twice?

Either cache() was called more than once on the same function — each call creates a separate memoized function with its own cache — or the arguments are not identity-equal, which happens whenever you pass a fresh object or array literal. Export a single cached instance and pass primitive arguments.

How long does the cache live?

One server request. React invalidates the cache for all memoized functions at the end of each request, so values never leak between users or between navigations. For caching that outlives a request, use your framework's data cache or an external store.

Can I use cache in a Client Component?

No — cache is documented as being for Server Components only, and client caching was removed from the API before React 19 shipped. In client code use useMemo for computed values, or keep the promise in a module-level cache or a data library.

More examples

Preloading: start the work early, await it late

The query starts while the page shell renders instead of when OrderList first awaits it, removing that wait from the critical path. Only one query runs: preloadOrders and OrderList call the same cached function with the same argument, so the second call joins the promise the first one created.

Example · jsx
// app/data.js
import { cache } from "react";
import { db } from "./db";

export const getOrders = cache(async userId =>
  db.order.findMany({ where: { userId }, orderBy: { id: "asc" } })
);

// Not awaited — it only warms the cache.
export function preloadOrders(userId) {
  void getOrders(userId);
}

// app/orders/page.jsx
import { getOrders, preloadOrders } from "../data.js";

async function OrderList({ userId }) {
  const orders = await getOrders(userId); // already in flight
  return (
    <ul>
      {orders.map(order => (
        <li key={order.id}>
          #{order.id} — {order.total}
        </li>
      ))}
    </ul>
  );
}

export default async function OrdersPage() {
  const userId = "user_1";

  // Start the query before rendering the slow header above the list.
  preloadOrders(userId);

  return (
    <main>
      <h1>Your orders</h1>
      <OrderList userId={userId} />
    </main>
  );
}

Two cache() calls means two caches

The first pair runs fetchStats twice per request even though the calls look identical, because cache(fn) returns a brand-new memoized function each time it is called. The second pair shows the argument rule: identity comparison means two structurally identical object literals are two different keys, while primitives behave as expected.

Example · jsx
// ❌ Each module wraps the same function separately.
// a.js
import { cache } from "react";
import { fetchStats } from "./stats";
export const getStats = cache(fetchStats); // cache A

// b.js
import { cache } from "react";
import { fetchStats } from "./stats";
export const getStats = cache(fetchStats); // cache B — never hits cache A

// ✅ Wrap once, import the same instance everywhere.
// stats-cached.js
import { cache } from "react";
import { fetchStats } from "./stats";
export const getStats = cache(fetchStats);

// Arguments are compared by identity, so object literals never hit:
await getStats({ range: "7d" }); // miss
await getStats({ range: "7d" }); // miss again — a different object

await getStats("7d"); // miss
await getStats("7d"); // hit — primitives compare equal

Discussion

  • Be the first to comment on this lesson.