remix-client-cache
Version:
Utility library to cache your client data in React Router
190 lines (189 loc) • 5.2 kB
JavaScript
// src/index.tsx
import { useEffect, useState } from "react";
import React from "react";
import {
Await,
useLoaderData,
useNavigate
} from "react-router";
var map = /* @__PURE__ */ new Map();
var CacheRoute = (Component, settings) => (
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(props) => {
const { cacheKey, invalidate, ...rest } = useCachedLoaderData(settings);
return /* @__PURE__ */ React.createElement(
Component,
{
...props,
loaderData: rest,
invalidate,
cacheKey
}
);
}
);
var cache = {
getItem: async (key) => map.get(key),
setItem: async (key, val) => map.set(key, val),
removeItem: async (key) => map.delete(key)
};
var augmentStorageAdapter = (storage) => {
return {
getItem: async (key) => {
try {
const item = JSON.parse(storage.getItem(key) || "");
return item;
} catch (e) {
return storage.getItem(key);
}
},
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
setItem: async (key, val) => storage.setItem(key, JSON.stringify(val)),
removeItem: async (key) => storage.removeItem(key)
};
};
var createCacheAdapter = (adapter) => {
if (typeof document === "undefined")
return { adapter: void 0 };
const adapterInstance = adapter();
if (adapterInstance instanceof Storage) {
return {
adapter: augmentStorageAdapter(adapterInstance)
};
}
return {
adapter: adapter()
};
};
var configureGlobalCache = (newCacheInstance) => {
if (typeof document === "undefined")
return;
const newCache = newCacheInstance();
if (newCache instanceof Storage) {
cache = augmentStorageAdapter(newCache);
return;
}
if (newCache) {
cache = newCache;
}
};
var decacheClientLoader = async ({ request, serverAction }, {
key = constructKey(request),
adapter = cache
}) => {
const data = await serverAction();
await adapter.removeItem(key);
return data;
};
var cacheClientLoader = async ({ request, serverLoader }, {
type = "swr",
key = constructKey(request),
adapter = cache
} = {
type: "swr",
key: constructKey(request),
adapter: cache
}) => {
const existingData = await adapter.getItem(key);
if (type === "normal" && existingData) {
return {
...existingData,
serverData: existingData,
deferredServerData: void 0,
key
};
}
const data = existingData ? existingData : await serverLoader();
await adapter.setItem(key, data);
const deferredServerData = existingData ? serverLoader() : void 0;
const dataToReturn = data != null ? data : existingData;
return {
...dataToReturn,
serverData: data,
deferredServerData,
key
};
};
var createClientLoaderCache = (props) => {
const clientLoader = (args) => cacheClientLoader(args, props);
clientLoader.hydrate = true;
return clientLoader;
};
function useCachedLoaderData({ adapter = cache } = { adapter: cache }) {
const loaderData = useLoaderData();
const navigate = useNavigate();
const [freshData, setFreshData] = useState({
..."serverData" in loaderData ? loaderData.serverData : loaderData
});
useEffect(() => {
let isMounted = true;
if (loaderData.deferredServerData) {
loaderData.deferredServerData.then((newData) => {
if (isMounted) {
adapter.setItem(loaderData.key, newData);
setFreshData(newData);
}
}).catch((e) => {
const res = e instanceof Response ? e : void 0;
if (res && res.status === 302) {
const to = res.headers.get("Location");
to && navigate(to);
} else {
throw e;
}
});
}
return () => {
isMounted = false;
};
}, [loaderData]);
useEffect(() => {
if (loaderData.serverData && JSON.stringify(loaderData.serverData) !== JSON.stringify(freshData)) {
setFreshData(loaderData.serverData);
}
}, [loaderData == null ? void 0 : loaderData.serverData]);
return {
...loaderData,
...freshData,
cacheKey: loaderData.key,
invalidate: () => invalidateCache(loaderData.key)
};
}
var constructKey = (request) => {
const url = new URL(request.url);
return url.pathname + url.search + url.hash;
};
var invalidateCache = async (key) => {
const keys = Array.isArray(key) ? key : [key];
for (const k of keys) {
await cache.removeItem(k);
}
};
var useCacheInvalidator = () => ({
invalidateCache
});
function useSwrData({
serverData,
deferredServerData,
...args
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
}) {
return function SWR({
children
}) {
return /* @__PURE__ */ React.createElement(React.Fragment, null, deferredServerData ? /* @__PURE__ */ React.createElement(React.Suspense, { fallback: children(serverData) }, /* @__PURE__ */ React.createElement(Await, { resolve: deferredServerData }, children)) : children(serverData != null ? serverData : args));
};
}
export {
CacheRoute,
cache,
cacheClientLoader,
configureGlobalCache,
createCacheAdapter,
createClientLoaderCache,
decacheClientLoader,
invalidateCache,
useCacheInvalidator,
useCachedLoaderData,
useSwrData
};