{"id":24896729,"url":"https://github.com/forge-42/remix-client-cache","last_synced_at":"2025-04-04T21:06:50.727Z","repository":{"id":219133577,"uuid":"748248108","full_name":"forge-42/remix-client-cache","owner":"forge-42","description":"Utility library to cache your client data in Remix.run","archived":false,"fork":false,"pushed_at":"2025-01-21T13:17:59.000Z","size":822,"stargazers_count":202,"open_issues_count":4,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-04-03T07:43:43.371Z","etag":null,"topics":["cache","caching","caching-library","react-router-v7"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forge-42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.MD","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["AlemTuzlak"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2024-01-25T15:29:31.000Z","updated_at":"2025-04-01T15:16:27.000Z","dependencies_parsed_at":"2024-12-24T01:33:46.154Z","dependency_job_id":"52b9be93-37a2-4e96-9242-4fab1c910fcb","html_url":"https://github.com/forge-42/remix-client-cache","commit_stats":{"total_commits":16,"total_committers":1,"mean_commits":16.0,"dds":0.0,"last_synced_commit":"37994f94e421300383b95a20f67180f605eef6a8"},"previous_names":["code-forge-net/remix-cache","code-forge-net/remix-client-cache","forge42dev/remix-client-cache","forge-42/remix-client-cache"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fremix-client-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fremix-client-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fremix-client-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fremix-client-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forge-42","download_url":"https://codeload.github.com/forge-42/remix-client-cache/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249524,"owners_count":20908212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","caching","caching-library","react-router-v7"],"created_at":"2025-02-01T20:06:34.251Z","updated_at":"2025-04-04T21:06:50.708Z","avatar_url":"https://github.com/forge-42.png","language":"TypeScript","funding_links":["https://github.com/sponsors/AlemTuzlak"],"categories":[],"sub_categories":[],"readme":"# remix-client-cache\n\n![GitHub Repo stars](https://img.shields.io/github/stars/Code-Forge-Net/remix-client-cache?style=social)\n![npm](https://img.shields.io/npm/v/remix-client-cache?style=plastic)\n![GitHub](https://img.shields.io/github/license/Code-Forge-Net/remix-client-cache?style=plastic)\n![npm](https://img.shields.io/npm/dy/remix-client-cache?style=plastic) \n![npm](https://img.shields.io/npm/dm/remix-client-cache?style=plastic) \n![GitHub top language](https://img.shields.io/github/languages/top/Code-Forge-Net/remix-client-cache?style=plastic) \n\n\u003cimg style=\"display: block; margin: 0 auto;\" src=\"./assets/remix-cache.png\" height=\"300px\" align=\"middle\" /\u003e\n\n# Important information\n\nThis library is now a part of the React Router ecosystem and runs on top of React Router. It should be compatible with remix.run but if you're having issues version\n1.1.0 is the last version that will work with remix.run.\n\n\nremix-client-cache is a powerful and lightweight library made for Remix.run to cache your server loader data on the client using clientLoaders.\n\nBy default it uses the stale while revalidate strategy and hot swaps your stale info once loaded from the server. It also allows you to invalidate the cache for a specific key or multiple keys.\n\nIt allows you to pass in an adapter of your choice to cache your data. \n\nIt comes with a default adapter that uses in memory storage to cache your data.\n\nFirst party support for localStorage, sessionStorage and localforage packages. You can just provide them as the argument to `configureGlobalCache`.\n\n## Install\n\n    npm install remix-client-cache\n\n## Basic usage\n\nHere is an example usage of remix-client-cache with the default in memory adapter.\n\n```tsx\nimport { json, type LoaderFunctionArgs } from \"@remix-run/node\";\nimport { ClientLoaderFunctionArgs } from \"@remix-run/react\";\n\nimport { cacheClientLoader, useCachedLoaderData } from \"remix-client-cache\";\n\nexport const loader = async ({ params }: LoaderFunctionArgs) =\u003e {\n  const response = await fetch(\n    `https://jsonplaceholder.typicode.com/users/${params.user}`\n  );\n  const user = await response.json();\n  await new Promise((resolve) =\u003e setTimeout(resolve, 1000));\n  return json({ user: { ...user, description: Math.random() } });\n};\n\n\n// Caches the loader data on the client\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args);\n  \n// make sure you turn this flag on\nclientLoader.hydrate = true;\n\nexport default function Index() {\n  // The data is automatically cached for you and hot swapped when refetched\n  const { user } = useCachedLoaderData\u003ctypeof loader\u003e(); \n\n  return (\n    \u003cdiv\u003e\n      {user.name} \u003chr /\u003e {user.email}\n      \u003chr /\u003e\n      {user.username}\n      \u003chr /\u003e\n      {user.website} \u003chr /\u003e\n      {user.description} \n    \u003c/div\u003e\n  );\n}\n\n```\n\n## Cache adapters\n\nThe library exports an interface you need to implement in order to create your own cache adapter. The interface is called `CacheAdapter`.\nIt closely matches the interface of `Storage` and requires you to have the following methods:\n\n- `getItem`: takes a key and returns a promise that resolves to the value stored at that key\n- `setItem`: takes a key and a value and returns a promise that resolves when the value is stored\n- `removeItem`: takes a key and returns a promise that resolves when the value is removed\n\nThe `cacheLoaderData` will use the default memory cache adapter that comes with the library. If you want an advanced use-case make sure that the adapter you provide implements the `CacheAdapter` interface.\n\n```ts\n// Inside your entry.client.tsx file \nimport { RemixBrowser } from \"@remix-run/react\";\nimport { startTransition, StrictMode } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\n\nimport { configureGlobalCache } from \"remix-client-cache\";\n\n// You can use the configureGlobalCache function to override the libraries default in-memory cache adapter\nconfigureGlobalCache(() =\u003e localStorage); // uses localStorage as the cache adapter\n\nstartTransition(() =\u003e {\n  hydrateRoot(\n    document,\n    \u003cStrictMode\u003e\n      \u003cRemixBrowser /\u003e\n    \u003c/StrictMode\u003e\n  );\n});\n\n```\n\nYou can use the `configureGlobalCache` function to override the libraries default in-memory cache adapter. It will globally switch to whatever adapter you provide to it.\n\nIf you want to have a per route adapter you can use the `createCacheAdapter` to create an adapter and provide it to your hooks and functions.\n\n```ts\n\nimport { createCacheAdapter, useCachedLoaderData } from \"remix-client-cache\";\n\nconst { adapter } = createCacheAdapter(() =\u003e localStorage); // uses localStorage as the cache adapter\n\n\n// Caches the loader data on the client\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args, { \n  // We pass our custom adapter to the clientLoader\n  adapter\n});\n  \n// make sure you turn this flag on\nclientLoader.hydrate = true;\n\nexport default function Index() {\n  const { user } = useCachedLoaderData\u003ctypeof loader\u003e({ \n    // We use the adapter returned by the createCacheAdapter function\n    adapter\n  });\n\n  return (\n    \u003cdiv\u003e\n      {user.name} \u003chr /\u003e {user.email}\n      \u003chr /\u003e\n      {user.username}\n      \u003chr /\u003e\n      {user.website} \u003chr /\u003e\n      {user.description} \n    \u003c/div\u003e\n  );\n}\n\n\n```\n\nHere are some examples of how you can use the library with different global adapters.\n\n```ts\nconfigureGlobalCache(() =\u003e localStorage); // uses localStorage as the cache adapter\nconfigureGlobalCache(() =\u003e sessionStorage); // uses sessionStorage as the cache adapter\nconfigureGlobalCache(() =\u003e localforage); // uses localforage as the cache adapter\n```\n\nAlso with different per route adapters:\n\n```ts\nconst { adapter } = createCacheAdapter(() =\u003e localStorage); // uses localStorage as the cache adapter\nconst { adapter } = createCacheAdapter(() =\u003e sessionStorage); // uses sessionStorage as the cache adapter\nconst { adapter } = createCacheAdapter(() =\u003e localforage); // uses localforage as the cache adapter\n```\n\nLet's say you want to use a custom adapter that uses a database to store the data. \n\nYou can do that by implementing the `CacheAdapter` interface and passing it to the `configureGlobalCache` or `createCacheAdapter` function.\n\n```ts\nclass DatabaseAdapter implements CacheAdapter {\n  async getItem(key: string) {\n    // get the item from the database\n  }\n\n  async setItem(key: string, value: string) {\n    // set the item in the database\n  }\n\n  async removeItem(key: string) {\n    // remove the item from the database\n  }\n}\n\nconfigureGlobalCache(() =\u003e new DatabaseAdapter()); // uses your custom adapter as the cache adapter globally\nconst { adapter } = createCacheAdapter(() =\u003e new DatabaseAdapter()); // uses your custom adapter as the cache adapter per route\n```\n \n\n## API's\n\n### createCacheAdapter\n\nFunction that creates a cache adapter and returns it. It takes one argument, the `adapter` that is used to store the data. \n\n```ts\nimport { createCacheAdapter } from \"remix-client-cache\";\n\nconst { adapter } = createCacheAdapter(() =\u003e localStorage); // uses localStorage as the cache adapter\n```\n\n### configureGlobalCache\n\nFunction that configures the global cache adapter. It takes one argument, the `adapter` that is used to store the data. \n\n```ts\nimport { configureGlobalCache } from \"remix-client-cache\";\n\nconfigureGlobalCache(() =\u003e localStorage); // uses localStorage as the cache adapter\n```\n\n\n### cacheClientLoader\n\nUsed to cache the data that is piped from the loader to your component using the `clientLoader` export. \n\nIt takes two arguments, the first one is the `ClientLoaderFunctionArgs` object that is passed to the `clientLoader` function, the second one is an object with the following properties:\n\n- `type` - that tells the client loader if it should use the normal caching mechanism where it stores the data and early returns that instead of refetching or if it should use the `staleWhileRevalidate` mechanism where it returns the cached data and refetches in the background. \n- `key` - key that is used to store the data in the cache. Defaults to the current route path including search params and hashes. (eg. /user/1?name=John#profile)\n- `adapter` - the cache adapter that is used to store the data. Defaults to the in memory adapter that comes with the library.\n \n\n```tsx\nimport { json, type LoaderFunctionArgs } from \"@remix-run/node\";\nimport { ClientLoaderFunctionArgs } from \"@remix-run/react\"; \nimport { cacheClientLoader, useCachedLoaderData } from \"remix-client-cache\";\n\nexport const loader = async ({ params }: LoaderFunctionArgs) =\u003e {\n  const response = await fetch(\n    `https://jsonplaceholder.typicode.com/users/${params.user}`\n  );\n  const user = await response.json();\n  await new Promise((resolve) =\u003e setTimeout(resolve, 1000));\n  return json({ user: { ...user, description: Math.random() } });\n};\n\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args, {\n  type: \"swr\", // default is swr, can also be set to normal\n  key: \"/user/1\" // default is the current route path including search params and hashes\n  adapter: () =\u003e localStorage // default is the in memory adapter, can be anything your wish\n});\nclientLoader.hydrate = true;\n\n```\n\n### createClientLoaderCache\n\nCreates everything needed to cache the data via clientLoader, behind the scenes creates the clientLoader object with the correct hydrate flag and the adapter.\n\n```tsx\nimport { createClientLoaderCache, cacheClientLoader } from \"remix-client-cache\";\n\nexport const clientLoader = createClientLoaderCache(); \n\n// above is equivalent to:\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args);\nclientLoader.hydrate = true;\n```\n\n### decacheClientLoader\n\nUsed to remove the data that is piped from the loader to your component using the `clientLoader` export. \n\n```tsx\nimport { json, type LoaderFunctionArgs } from \"@remix-run/node\";\nimport { ClientLoaderFunctionArgs } from \"@remix-run/react\"; \nimport { decacheClientLoader, useCachedLoaderData } from \"remix-client-cache\";\n\nexport const loader = async ({ params }: LoaderFunctionArgs) =\u003e {\n  const response = await fetch(\n    `https://jsonplaceholder.typicode.com/users/${params.user}`\n  );\n  const user = await response.json();\n  await new Promise((resolve) =\u003e setTimeout(resolve, 1000));\n  return json({ user: { ...user, description: Math.random() } });\n};\n// The data is cached here\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader;\nclientLoader.hydrate = true;\n// It is de-cached after a successful action submission via the clientAction export\nexport const clientAction = decacheClientLoader;\n\n```\n\nAccepts an optional object with the following properties:\n- `key` - key that is used to store the data in the cache.\n- `adapter` - the cache adapter that is used to store the data.\n\n\n\n### useCachedLoaderData\n\nHook that can be used to get the cached data from the `clientLoader` export. Must be used together with `cacheClientLoader`  because the data returned from\nthe `cacheClientLoader` is augmented to work with `useCachedLoaderData` in mind and not the standard `useLoaderData` hook.\n\n```tsx\nimport { useCachedLoaderData } from \"remix-client-cache\";\n\n// Must be used together with cacheClientLoader\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args, \"swr\");\nclientLoader.hydrate = true;\n\nexport default function Index() {\n  // The data is automatically cached for you and hot swapped when refetched\n  const { user } = useCachedLoaderData\u003ctypeof loader\u003e(); \n\n  return (\n    \u003cdiv\u003e\n      {user.name} \u003chr /\u003e {user.email}\n      \u003chr /\u003e\n      {user.username}\n      \u003chr /\u003e\n      {user.website} \u003chr /\u003e\n      {user.description} \n    \u003c/div\u003e\n  );\n}\n``` \n\nAccepts an optional object with the following properties:\n- `adapter` - the cache adapter that is used to store the data. Defaults to the in memory adapter that comes with the library.\n \n\n \n### useSwrData\n\nHook used to get an SWR component that hot swaps the data for you. It takes one argument, loaderData returned by the `useCachedLoaderData` OR `useLoaderData` hook. \n\n```tsx\nimport { useCachedLoaderData, useSwrData } from \"remix-client-cache\";\n\nexport const clientLoader = (args: ClientLoaderFunctionArgs) =\u003e cacheClientLoader(args);\nclientLoader.hydrate = true;\n\nexport default function Index() {\n  // We do not destructure the data so we can pass in the object into the useSwrData hook\n  const loaderData = useLoaderData\u003ctypeof loader\u003e(); \n  // You can also use useCachedLoaderData hook with the useSwrData hook\n  const loaderData = useCachedLoaderData\u003ctypeof loader\u003e(); \n  // Pass the loader data into the hook and the component will handle everything else for you\n  const SWR = useSwrData(loaderData);\n\n  return (\n    \u003cSWR\u003e\n      {/** Hot swapped automatically */}\n      {({ user }) =\u003e (\n        \u003cdiv\u003e\n          {data.name} \u003chr /\u003e {data.email}\n          \u003chr /\u003e\n          {data.username}\n          \u003chr /\u003e\n          {data.website} \u003chr /\u003e\n          {data.description} \n        \u003c/div\u003e\n      )}\n    \u003c/SWR\u003e\n  );\n}\n```\n\n### invalidateCache\n\nUtility function that can be used to invalidate the cache for a specific key. It takes one argument, the `key` that is used to store the data in the cache. \nCan also be an array of keys\n\n```ts\nimport { invalidateCache } from \"remix-client-cache\";\n\ninvalidateCache(\"/user/1\"); // invalidates the cache for the /user/1 route\n```\n\nKeep in mind this can only be used on the client, so either in `clientLoader` or `clientAction` exports or the components themselves.\n\n### useCacheInvalidator\n\nHook that returns a function that can be used to invalidate the cache for a specific key. It takes one argument, the `key` that is used to store the data in the cache. Can also be an array of keys\n\n```tsx\nimport { useCacheInvalidator } from \"remix-client-cache\";\n\nexport default function Index() {\n  const { invalidateCache } = useCacheInvalidator(); \n\n  return (\n    \u003cdiv\u003e\n      // invalidates the cache for the /user/1 route\n      \u003cbutton onClick={ () =\u003e invalidateCache(\"/user/1\") }\u003eInvalidate cache\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n``` \n\n## Support \n\nIf you like the project, please consider supporting us by giving a ⭐️ on Github.\n\n\n## License\n\nMIT\n\n## Bugs\n\nIf you find a bug, please file an issue on [our issue tracker on GitHub](https://github.com/Code-Forge-Net/remix-client-cache/issues)\n\n\n## Contributing\n\nThank you for considering contributing to remix-client-cache! We welcome any contributions, big or small, including bug reports, feature requests, documentation improvements, or code changes.\n\nTo get started, please fork this repository and make your changes in a new branch. Once you're ready to submit your changes, please open a pull request with a clear description of your changes and any related issues or pull requests.\n\nPlease note that all contributions are subject to our [Code of Conduct](https://github.com/Code-Forge-Net/remix-client-cache/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.\n\nWe appreciate your time and effort in contributing to remix-client-cache and helping to make it a better tool for the community!\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge-42%2Fremix-client-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforge-42%2Fremix-client-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge-42%2Fremix-client-cache/lists"}