Skip to content

Commit a37c63c

Browse files
authored
fix: use cache in a function with params (#66)
Fixes `"use cache"` when using the directive in a function with params where the param should be included in the cache key. Changes in-memory cache implementation to use `deep-eql` for deep equality check on the cache key segments.
1 parent b3c6f79 commit a37c63c

File tree

8 files changed

+64
-49
lines changed

8 files changed

+64
-49
lines changed

packages/react-server/lib/plugins/use-cache-inline.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export default function useServerInline(profiles) {
224224
],
225225
};
226226
cache.node.body.body = [
227-
...(cache.params.length > 0
227+
...(cache.params.length > 0 || cache.node.params.length > 0
228228
? [
229229
{
230230
type: "VariableDeclaration",
@@ -263,7 +263,10 @@ export default function useServerInline(profiles) {
263263
name: "useCache",
264264
},
265265
arguments: [
266-
cacheKey,
266+
{
267+
...cacheKey,
268+
elements: [...cacheKey.elements, ...cache.node.params],
269+
},
267270
{
268271
type: "FunctionExpression",
269272
id: null,

packages/react-server/memory-cache/index.mjs

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import equals from "deep-eql";
2+
13
import { context$, getContext } from "../server/context.mjs";
24
import {
35
CACHE_CONTEXT,
@@ -27,9 +29,7 @@ export class MemoryCache {
2729

2830
const cacheKeys = this.cache.keys();
2931
for (const entryKeys of cacheKeys) {
30-
if (
31-
keys.every((key, keyIndex) => entryKeys[keyIndex] === key?.toString())
32-
) {
32+
if (keys.every((key, keyIndex) => equals(entryKeys[keyIndex], key))) {
3333
return this.cache.get(entryKeys);
3434
}
3535
}
@@ -41,26 +41,19 @@ export class MemoryCache {
4141
if (await this.hasExpiry(keys)) {
4242
const cacheKeys = this.cache.keys();
4343
for (const entryKeys of cacheKeys) {
44-
if (
45-
keys.every((key, keyIndex) => entryKeys[keyIndex] === key?.toString())
46-
) {
44+
if (keys.every((key, keyIndex) => equals(entryKeys[keyIndex], key))) {
4745
this.cache.set(entryKeys, value);
4846
return;
4947
}
5048
}
51-
this.cache.set(
52-
keys.map((key) => key?.toString()),
53-
value
54-
);
49+
this.cache.set(keys, value);
5550
}
5651
}
5752

5853
async has(keys) {
5954
const cacheKeys = this.cache.keys();
6055
for (const entryKeys of cacheKeys) {
61-
if (
62-
keys.every((key, keyIndex) => entryKeys[keyIndex] === key?.toString())
63-
) {
56+
if (keys.every((key, keyIndex) => equals(entryKeys[keyIndex], key))) {
6457
return true;
6558
}
6659
}
@@ -71,25 +64,20 @@ export class MemoryCache {
7164
async setExpiry(keys, expiry) {
7265
const expiryKeys = this.expiry.keys();
7366
for (const entryKeys of expiryKeys) {
74-
if (
75-
keys.every((key, keyIndex) => entryKeys[keyIndex] === key?.toString())
76-
) {
67+
if (keys.every((key, keyIndex) => equals(entryKeys[keyIndex], key))) {
7768
this.expiry.set(entryKeys, expiry);
7869
return;
7970
}
8071
}
81-
this.expiry.set(
82-
keys.map((key) => key?.toString()),
83-
expiry
84-
);
72+
this.expiry.set(keys, expiry);
8573
}
8674

8775
async hasExpiry(keys) {
8876
const expiryKeys = this.expiry.keys();
8977
for (const entryKeys of expiryKeys) {
9078
for (let keyIndex = 0; keyIndex < entryKeys.length; keyIndex++) {
9179
const key = keys[keyIndex];
92-
if (entryKeys[keyIndex] !== key?.toString()) {
80+
if (!equals(entryKeys[keyIndex], key)) {
9381
break;
9482
}
9583
if (keyIndex === entryKeys.length - 1) {
@@ -104,9 +92,7 @@ export class MemoryCache {
10492
async delete(keys) {
10593
const cacheKeys = this.cache.keys();
10694
for (const entryKeys of cacheKeys) {
107-
if (
108-
keys.every((key, keyIndex) => entryKeys[keyIndex] === key?.toString())
109-
) {
95+
if (keys.every((key, keyIndex) => equals(entryKeys[keyIndex], key))) {
11096
this.cache.delete(entryKeys);
11197
}
11298
}

packages/react-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"ansi-regex": "^6.0.1",
9797
"cac": "^6.7.14",
9898
"chokidar": "^3.5.3",
99+
"deep-eql": "^5.0.2",
99100
"esbuild": "^0.19.3",
100101
"escodegen": "^2.1.0",
101102
"estraverse": "^5.3.0",

packages/react-server/server/cache.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function withCache(Component, ttl = Infinity) {
1111
}
1212

1313
export function useResponseCache(ttl = Infinity) {
14-
const url = useUrl()?.toString();
14+
const url = useUrl();
1515
const accept = useRequest()?.headers?.get?.("accept");
1616
const outlet = useOutlet();
1717
const cache = getContext(CACHE_CONTEXT);

pnpm-lock.yaml

Lines changed: 20 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/__test__/basic.spec.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,18 @@ test("use cache concurrency", async () => {
224224
]);
225225
expect(JSON.stringify(serverLogs)).toBe(`["getTodos"]`);
226226
});
227+
228+
test("use cache dynamic", async () => {
229+
await server("fixtures/use-cache-dynamic.jsx");
230+
await page.goto(hostname + "?id=1");
231+
const time = await page.textContent("body");
232+
233+
await page.reload();
234+
expect(await page.textContent("body")).toBe(time);
235+
236+
await page.goto(hostname + "?id=2");
237+
expect(await page.textContent("body")).not.toBe(time);
238+
239+
await page.goto(hostname + "?id=1");
240+
expect(await page.textContent("body")).toBe(time);
241+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { invalidate, useSearchParams } from "@lazarv/react-server";
2+
3+
function getTime({ id: _ }) {
4+
"use cache";
5+
return new Date().toISOString();
6+
}
7+
8+
export default async function App() {
9+
const { id } = useSearchParams();
10+
return <div>{getTime({ id })}</div>;
11+
}

test/vitest.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default defineConfig({
2323
? ["dot", "github-actions"]
2424
: ["default"],
2525
pool: "forks",
26-
fileParallelism: false,
26+
fileParallelism: !process.env.CI,
2727
},
2828
publicDir: false,
2929
});

0 commit comments

Comments
 (0)