code-forge-io / code-forge-io/remix-client-cache

Support caching for streamed responses

Aperta
#7 3 commenti 1 reazione 0 assegnatari Vedi su GitHub
Lingua principale
TypeScript
Stelle
247
Fork
3
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

[Stackblitz](https://stackblitz.com/~/github.com/predaytor/remix-test?file=app/routes/test.tsx)

When using `defer` to stream a response from a loader that returned a promise which later used in a boundary, we cannot properly serialize the promise to store say in localStorage or IndexedDB (`JSON.stringify` will result in an empty object `{}`). The only way to store this type of data is directly in memory, so on the next navigation, `clientLoader` will return fulfilled promise with the data, rather than calling the server `loader'.
```tsx
let cache: SerializeFrom;
export const clientLoader = defineClientLoader(async ({ serverLoader }) => {
if (!cache) {
cache = await serverLoader();
}

return cache;
});

clientLoader.hydrate = true;
```
By using `remix-client-cache`, we can create an adapter for when we need to cache streaming responses, rather than relying on a global setting using, for example, `localStorage`.
```ts
import { lru } from 'tiny-lru';
import { type CacheAdapter, createCacheAdapter } from 'remix-client-cache';

const cache = lru(100);

class LRUAdapter implements CacheAdapter {
async getItem(key: string) {
return cache.get(key);
}

async setItem(key: string, value: any) {
return cache.set(key, value);
}

async removeItem(key: string) {
return cache.delete(key);
}
}

export const { adapter: lruAdapter } = createCacheAdapter(() => new LRUAdapter());
```

`routes/index.tsx`:
```tsx
import { defer } from '@remix-run/node';
import { Await, ClientLoaderFunctionArgs, Link } from '@remix-run/react';
import { Suspense } from 'react';
import { cacheClientLoader, useCachedLoaderData } from 'remix-client-cache';
import { lruAdapter } from '~/client-cache-adapter';

async function getQueryData() {
await new Promise((resolve) => setTimeout(resolve, 3000));
return { data: [{ id: 1 }] };
}

export async function loader() {
const query = getQueryData();

return defer({
query,
});
}

export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args, { adapter: lruAdapter });
clientLoader.hydrate = true;

export default function Page() {
const { query } = useCachedLoaderData();

return (
<>
Home






{({ data }) => (


    {data?.map((entry) =>
  • {entry.id}
  • )}

)}



);
}
```
This opens up a question whether it's a good idea to store data in server memory, it would be interesting if we could transform fulfilled promises using a library like [`turbo-stream`](https://github.com/jacob-ebey/turbo-stream) to store on the client-side or use a web worker and decode to the original form for consumption by .

---

There is currently a bug where a returned promise from the cache has already been fulfilled, the internal logic of `remix-client-cache` cannot understand when a revalidation should occur, or fulfilled data is currently present, since we must store promises directly in memory and not as a string.
https://github.com/forge42dev/remix-client-cache/blob/main/src/index.tsx#L116-L140
```tsx
// Unpack deferred data from the server
useEffect(() => {
let isMounted = true;
if (loaderData.deferredServerData) {
loaderData.deferredServerData.then((newData: any) => {
if (isMounted) {
adapter.setItem(loaderData.key, newData);
setFreshData(newData);
}
});
}
return () => {
isMounted = false;
};
}, [loaderData, adapter]);

// Update the cache if the data changes
useEffect(() => {
if (
loaderData.serverData &&
JSON.stringify(loaderData.serverData) !== JSON.stringify(freshData)
) {
setFreshData(loaderData.serverData);
}
}, [loaderData?.serverData, freshData]);
```

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start with src/index.tsx lines 116-140 and reproduce the fulfilled-promise path from the linked StackBlitz/routes/index.tsx example. Trace how deferredServerData, serverData, freshData, and adapter are compared; done means the cache distinguishes fulfilled cached data from data requiring revalidation with verified behavior.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
react, typescript
Ambito
frontend, performance
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
30/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.