{"id":16696585,"url":"https://github.com/karolis-sh/redux-cached-api-middleware","last_synced_at":"2025-03-23T14:31:43.541Z","repository":{"id":38337193,"uuid":"140337160","full_name":"karolis-sh/redux-cached-api-middleware","owner":"karolis-sh","description":"Caching APIs with redux easily.","archived":false,"fork":false,"pushed_at":"2023-10-27T14:17:05.000Z","size":2889,"stargazers_count":21,"open_issues_count":2,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-18T13:29:41.307Z","etag":null,"topics":["api","caching","redux","redux-api-middleware"],"latest_commit_sha":null,"homepage":"https://rcam-demo.netlify.app","language":"JavaScript","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/karolis-sh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-09T20:12:56.000Z","updated_at":"2024-05-10T18:29:16.000Z","dependencies_parsed_at":"2024-10-28T16:38:45.440Z","dependency_job_id":null,"html_url":"https://github.com/karolis-sh/redux-cached-api-middleware","commit_stats":null,"previous_names":["buz-zard/redux-cached-api-middleware"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karolis-sh%2Fredux-cached-api-middleware","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karolis-sh%2Fredux-cached-api-middleware/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karolis-sh%2Fredux-cached-api-middleware/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karolis-sh%2Fredux-cached-api-middleware/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karolis-sh","download_url":"https://codeload.github.com/karolis-sh/redux-cached-api-middleware/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245115982,"owners_count":20563263,"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":["api","caching","redux","redux-api-middleware"],"created_at":"2024-10-12T17:44:12.858Z","updated_at":"2025-03-23T14:31:43.123Z","avatar_url":"https://github.com/karolis-sh.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# redux-cached-api-middleware\n\n[![npm version][version-badge]][version]\n![Node.js CI](https://github.com/karolis-sh/redux-cached-api-middleware/workflows/Node.js%20CI/badge.svg)\n[![License: MIT][license-badge]][license]\n[![gzip size][gzip-badge]][unpkg-bundle]\n[![code style: prettier][code-style-badge]][code-style]\n\n\u003e Redux module that makes working with APIs a breeze.\n\n## Table of Contents\n\n- [Why](#why)\n- [Installation](#installation)\n- [Example](#example)\n- [API](#api)\n  - [API Config](#api-config)\n    - [DEFAULT_INVOKE_OPTIONS](#DEFAULT_INVOKE_OPTIONS)\n    - [DEFAULT_CACHE_STRATEGY](#DEFAULT_CACHE_STRATEGY)\n  - [Redux Actions](#redux-actions)\n    - [invoke()](#invoke)\n    - [invalidateCache()](#invalidatecache)\n    - [clearCache()](#clearcache)\n  - [Redux Selectors](#redux-selectors)\n    - [getResult()](#getresult)\n  - [Caching Strategies](#caching-strategies)\n- [Demos](#demos)\n- [Other Solutions](#other-solutions)\n- [References](#references)\n- [License](#license)\n\n## Why\n\nCaching API responses can greatly increase UX by saving network\nbandwidth and not showing loaders for the same resources all over again while\nuser navigates the application. You can also create a fluid returning UX in\ncombination with persistance libraries, e.g., [`redux-persist`][redux-persist].\n\nThe [`redux-api-middleware`][redux-api-middleware] library is pretty\nstandardized and popular way to interact with APIs using redux, that's why it was\nchosen as a base for this package.\n\n## Installation\n\n1. Install dependencies:\n\n```bash\n$ npm install --save redux-cached-api-middleware redux-api-middleware redux-thunk\n```\n\nor\n\n```bash\n$ yarn add redux-cached-api-middleware redux-api-middleware redux-thunk\n```\n\n\\* You can also consume this package via `\u003cscript\u003e` tag in browser from [`UMD`][umd-link]\nbuild. The UMD builds make redux-cached-api-middleware available as a\nwindow.ReduxCachedApiMiddleware global variable.\n\n\u003c!-- markdownlint-disable MD029 --\u003e\n\n2. Setup `redux`:\n\n\u003c!-- markdownlint-enable MD029 --\u003e\n\n```javascript\nimport { createStore, combineReducers, applyMiddleware } from 'redux';\nimport thunk from 'redux-thunk';\nimport { apiMiddleware } from 'redux-api-middleware';\nimport api from 'redux-cached-api-middleware';\nimport reducers from './reducers';\n\nconst store = createStore(\n  combineReducers({\n    ...reducers,\n    [api.constants.NAME]: api.reducer,\n  }),\n  applyMiddleware(thunk, apiMiddleware)\n);\n```\n\n## Example\n\nA simple `ExampleApp` component that invokes API endpoint on mount with `TTL_SUCCESS`\ncache strategy of 10 minutes. This means that if items were fetched in the past\n10 minutes successfully, the cached value will be returned, otherwise new fetch\nrequest will happen.\n\n```js\nimport React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport api from 'redux-cached-api-middleware';\nimport Items from './Items';\nimport Error from './Error';\n\nclass ExampleApp extends React.Component {\n  componentDidMount() {\n    this.props.fetchData();\n  }\n\n  render() {\n    const { result } = this.props;\n    if (!result) return null;\n    if (result.fetching) return \u003cdiv\u003eLoading...\u003c/div\u003e;\n    if (result.error) return \u003cError data={result.errorPayload} /\u003e;\n    if (result.successPayload) return \u003cItems data={result.successPayload} /\u003e;\n    return \u003cdiv\u003eNo items\u003c/div\u003e;\n  }\n}\n\nExampleApp.propTypes = {\n  fetchData: PropTypes.func.isRequired,\n  result: PropTypes.shape({\n    fetching: PropTypes.bool,\n    fetched: PropTypes.bool,\n    error: PropTypes.bool,\n    timestamp: PropTypes.number,\n    successPayload: PropTypes.any,\n    errorPayload: PropTypes.any,\n  }),\n};\n\nconst CACHE_KEY = 'GET/items';\n\nconst enhance = connect(\n  (state) =\u003e ({\n    result: api.selectors.getResult(state, CACHE_KEY),\n  }),\n  (dispatch) =\u003e ({\n    fetchData() {\n      return dispatch(\n        api.actions.invoke({\n          method: 'GET',\n          headers: { Accept: 'application/json' },\n          endpoint: 'https://my-api.com/items/',\n          cache: {\n            key: CACHE_KEY,\n            strategy: api.cache\n              .get(api.constants.CACHE_TYPES.TTL_SUCCESS)\n              .buildStrategy({ ttl: 10 * 60 * 1000 }), // 10 minutes\n          },\n        })\n      );\n    },\n  })\n);\n\nexport default enhance(ExampleApp);\n```\n\n## API\n\n### API Config\n\n#### `DEFAULT_INVOKE_OPTIONS`\n\nThe default [redux-api-middleware RSAA options][redux-api-middleware-options] object\nthat later will be merged when calling every [`invoke`](#invoke) action - e.g.:\n\n```js\napi.config.DEFAULT_INVOKE_OPTIONS = {\n  method: 'GET',\n  headers: {\n    Accept: 'application/json',\n    'Content-Type': 'application/json',\n  },\n};\n```\n\n\\* Options get merged using `Object.assign({}, DEFAULT_INVOKE_OPTIONS, invokeOptions)`\nin `invoke` action.\n\n#### `DEFAULT_CACHE_STRATEGY`\n\nThe default [caching strategy](#caching-strategies) that will be used when\ncalling every [`invoke`](#invoke) action - e.g.:\n\n```js\napi.config.DEFAULT_CACHE_STRATEGY = api.cache\n  .get(api.constants.CACHE_TYPES.TTL_SUCCESS)\n  .buildStrategy({ ttl: 600000 });\n```\n\n### Redux Actions\n\n#### `invoke()`\n\nCall API endpoints anywhere and retrieve data with redux selectors.\n\n```js\ndispatch(api.actions.invoke((options: InvokeOptions)));\n```\n\nThe `invoke` action response will be `undefined` if there was a valid cached\nvalue in redux state, otherwise `invoke` will return `redux-api-middleware` response.\n\n`InvokeOptions` is an extended version of [redux-api-middleware options][redux-api-middleware-options].\nYou can use `invoke` like an `RSAA` action wrapper without any caching.\nTo start using caching possibilities you need pass `cache` object. You have to\nprovide unique `key` value and either a caching `strategy` or `shouldFetch` function.\n\n- Cache `strategy` - use one of pre-defined [caching strategies](#caching-strategies)\n  to defined at what state resource is valid or not:\n\n```js\napi.actions.invoke({\n  method: 'GET',\n  headers: { Accept: 'application/json' },\n  endpoint: 'https://my-api.com/items/',\n  cache: {\n    key: 'GET/my-api.com/items',\n    strategy: api.cache.get(api.constants.CACHE_TYPES.TTL_SUCCESS).buildStrategy({\n      ttl: 600000, // 10 minutes\n    }),\n  },\n});\n```\n\n- `shouldFetch` function - a custom function to defined when resource valid:\n\n```js\napi.actions.invoke({\n  method: 'GET',\n  headers: { Accept: 'application/json' },\n  endpoint: 'https://my-api.com/items/',\n  cache: {\n    key: 'GET/my-api.com/items',\n    shouldFetch({ state: CachedApiState }) {\n      // Define your logic when the resource should be re-fetched\n      return true;\n    },\n  },\n});\n```\n\n\\* Check [`getResult` selector docs](#getresult) for `CachedApiState` structure.\n\n#### `invalidateCache()`\n\nIf you're restoring redux state from offline storage, there might be some interrupted\nfetch requests - which can restore your app in a broken state. You can\ninvalidate all the cached redux state, or selectively with `cacheKey`.\n\n```js\ndispatch(\n  api.actions.invalidateCache(\n    (cacheKey: ?string) // unique cache key\n  )\n);\n```\n\n#### `clearCache()`\n\nClear all the cached redux state, or selectively with `cacheKey`.\n\n```js\ndispatch(\n  api.actions.clearCache(\n    (cacheKey: ?string) // unique cache key\n  )\n);\n```\n\n### Redux Selectors\n\n#### `getResult()`\n\nSelect all information about API request.\n\n```js\nconst response: ?CachedApiState = api.selectors.getResult(\n  (state: Object), // redux state\n  (cacheKey: string) // unique cache key\n);\n```\n\nThe selected `CachedApiState` object has a structure of:\n\n```js\n{\n  fetching: boolean, // is fetching in progress\n  fetched: boolean, // was any fetch completed\n  error: boolean, // was last response an error\n  timestamp: ?number, // last response timestamp\n  successPayload: ?any, // last success response payload\n  errorPayload: ?any, // last error response payload\n}\n```\n\n\\* If `getResult` response is `undefined` it means the API request wasn't\ninitialized yet.\n\n### Caching Strategies\n\n- `SIMPLE_SUCCESS` - uses previous successful fetch result\n\n```js\nconst strategy = api.cache.get(api.constants.CACHE_TYPES.SIMPLE_SUCCESS).buildStrategy();\n```\n\n- `SIMPLE` - uses any previous payload fetch result\n\n```js\nconst strategy = api.cache.get(api.constants.CACHE_TYPES.SIMPLE).buildStrategy();\n```\n\n- `TTL_SUCCESS` - uses previous successful fetch result if time to live (TTL)\n  was not reached\n\n```js\nconst strategy = api.cache.get(api.constants.CACHE_TYPES.TTL_SUCCESS).buildStrategy({\n  ttl: 1000,\n});\n```\n\n- `TTL` - uses any previous fetch result if TTL was not reached\n\n```js\nconst strategy = api.cache.get(api.constants.CACHE_TYPES.TTL).buildStrategy({\n  ttl: 1000,\n});\n```\n\n## Demos\n\n- [Crypto prices][crypto-demo] ([source code][crypto-demo-src]) - cached\n  API requests that sync with localStorage\n- [Codepen demo][codepen-demo] - GutHub user repository searcher\n- [Demos monorepo][rcam-demos] - various demos using `create-react-app` etc.\n\n## Other Solutions\n\nThere are other solutions if `redux-cached-api-middleware` doesn't fit your needs:\n\n- [`redux-cache`](https://github.com/JumboInteractiveLimited/redux-cache)\n\n## References\n\n- [`redux`](https://redux.js.org)\n- [`redux-thunk`][redux-thunk]\n- [`redux-api-middleware`][redux-api-middleware]\n- [`redux-persist`][redux-persist] - sync redux store\n  with local (or any other) storage\n\n[version-badge]: https://badge.fury.io/js/redux-cached-api-middleware.svg\n[version]: https://www.npmjs.com/package/redux-cached-api-middleware\n[license-badge]: https://img.shields.io/badge/License-MIT-yellow.svg\n[license]: https://opensource.org/licenses/MIT\n[code-style-badge]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg\n[code-style]: https://github.com/prettier/prettier\n[gzip-badge]: http://img.badgesize.io/https://unpkg.com/redux-cached-api-middleware/lib/index.js?compression=gzip\u0026label=gzip%20size\n[unpkg-bundle]: https://unpkg.com/redux-cached-api-middleware/lib/\n[umd-link]: https://unpkg.com/redux-cached-api-middleware/umd\n[crypto-demo]: https://redux-cached-api-middleware-demo.netlify.app\n[crypto-demo-src]: https://github.com/karolis-sh/redux-cached-api-middleware/tree/master/demo\n[codepen-demo]: https://codepen.io/karolis-sh/pen/XByZyP\n[rcam-demos]: https://github.com/karolis-sh/rcam-demos\n[redux-thunk]: https://github.com/reduxjs/redux-thunk\n[redux-api-middleware]: https://www.npmjs.com/package/redux-api-middleware\n[redux-api-middleware-options]: https://github.com/agraboso/redux-api-middleware#defining-the-api-call\n[redux-persist]: https://www.npmjs.com/package/redux-persist\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarolis-sh%2Fredux-cached-api-middleware","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarolis-sh%2Fredux-cached-api-middleware","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarolis-sh%2Fredux-cached-api-middleware/lists"}