Skip to content
Caching APIs with redux easily.
JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
demo build(npm): drop yarn in favour of npm Nov 25, 2018
src
.babelrc chore(package): upgrade to babel@7 Nov 25, 2018
.editorconfig chore(eslint): add eslint and prettier setup Jul 9, 2018
.eslintignore build(umd): add umd build, change the way es module is built Aug 9, 2018
.eslintrc test(lint): remove redundant eslint rule Aug 6, 2018
.gitignore build(npm): drop yarn in favour of npm Nov 25, 2018
.markdownlint.json docs(readme): update installation and references sections Jul 25, 2018
.npmignore build(umd): add umd build, change the way es module is built Aug 9, 2018
.nvmrc chore(nvm): bump node version Aug 5, 2018
.prettierignore chore(workflow): add lint-staged precommit hooks Nov 25, 2018
.prettierrc chore(eslint): add eslint and prettier setup Jul 9, 2018
.travis.yml ci(greenkeeper): fix multiple node versions build error Dec 11, 2018
LICENSE chore(license): update author Jul 23, 2018
README.md chore(workflow): add lint-staged precommit hooks Nov 25, 2018
package-lock.json chore(package): update lockfile package-lock.json Jul 19, 2019
package.json chore(package): update cz-conventional-changelog to version 3.0.0 Jul 19, 2019
postcss.config.js chore(demo): add info, style Aug 2, 2018
rollup.config.js chore(package): upgrade to babel@7 Nov 25, 2018
tailwind.js chore(demo): add info, style Aug 2, 2018

README.md

redux-cached-api-middleware

Redux module that makes working with APIs a breeze.

npm version Build Status codecov.io License: MIT gzip size size module formats: umd, cjs and es semantic-release Greenkeeper badge code style: prettier

Table of Contents

Why

Caching API responses can greatly increase UX by saving network bandwidth and not showing loaders for the same resources all over again while user navigates the application. You can also create a fluid returning UX in combination with persistance libraries, e.g., redux-persist.

The redux-api-middleware library is pretty standardized and popular way to interact with APIs using redux, that's why it was chosen as a base for this package.

Installation

  1. Install dependencies:
$ npm install --save redux-cached-api-middleware redux-api-middleware redux-thunk

or

$ yarn add redux-cached-api-middleware redux-api-middleware redux-thunk

* You can also consume this package via <script> tag in browser from UMD build. The UMD builds make redux-cached-api-middleware available as a window.ReduxCachedApiMiddleware global variable.

  1. Setup redux:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { apiMiddleware } from 'redux-api-middleware';
import api from 'redux-cached-api-middleware';
import reducers from './reducers';

const store = createStore(
  combineReducers({
    ...reducers,
    [api.constants.NAME]: api.reducer,
  }),
  applyMiddleware(thunk, apiMiddleware)
);

Example

A simple ExampleApp component that invokes API endpoint on mount with TTL_SUCCESS cache strategy of 10 minutes. This means that if items were fetched in the past 10 minutes successfully, the cached value will be returned, otherwise new fetch request will happen.

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import api from 'redux-cached-api-middleware';
import Items from './Items';
import Error from './Error';

class ExampleApp extends React.Component {
  componentDidMount() {
    this.props.fetchData();
  }

  render() {
    const { result } = this.props;
    if (!result) return null;
    if (result.fetching) return <div>Loading...</div>;
    if (result.error) return <Error data={result.errorPayload} />;
    if (result.successPayload) return <Items data={result.successPayload} />;
    return <div>No items</div>;
  }
}

ExampleApp.propTypes = {
  fetchData: PropTypes.func.isRequired,
  result: PropTypes.shape({}),
};

const CACHE_KEY = 'GET/items';

const enhance = connect(
  state => ({
    result: api.selectors.getResult(state, CACHE_KEY),
  }),
  dispatch => ({
    fetchData() {
      return dispatch(
        api.actions.invoke({
          method: 'GET',
          headers: { Accept: 'application/json' },
          endpoint: 'https://my-api.com/items/',
          cache: {
            key: CACHE_KEY,
            strategy: api.cache
              .get(api.constants.CACHE_TYPES.TTL_SUCCESS)
              .buildStrategy({ ttl: 10 * 60 * 1000 }), // 10 minutes
          },
        })
      );
    },
  })
);

export default enhance(ExampleApp);

API

API Config

DEFAULT_INVOKE_OPTIONS

The default redux-api-middleware RSAA options object that later will be merged when calling every invoke action - e.g.:

api.config.DEFAULT_INVOKE_OPTIONS = {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
};

* Options get merged using Object.assign({}, DEFAULT_INVOKE_OPTIONS, invokeOptions) in invoke action.

DEFAULT_CACHE_STRATEGY

The default caching strategy that will be used when calling every invoke action - e.g.:

api.config.DEFAULT_CACHE_STRATEGY = api.cache
  .get(api.constants.CACHE_TYPES.TTL_SUCCESS)
  .buildStrategy({ ttl: 600000 });

Redux Actions

invoke()

Call API endpoints anywhere and retrieve data with redux selectors.

dispatch(api.actions.invoke(
  options: InvokeOptions,
));

The invoke action response will be undefined if there was a valid cached value in redux state, otherwise invoke will return redux-api-middleware response.

InvokeOptions is an extended version of redux-api-middleware options. You can use invoke like an RSAA action wrapper without any caching. To start using caching possibilities you need pass cache object. You have to provide unique key value and either a caching strategy or shouldFetch function.

  • Cache strategy - use one of pre-defined caching strategies to defined at what state resource is valid or not:
api.actions.invoke({
  method: 'GET',
  headers: { Accept: 'application/json' },
  endpoint: 'https://my-api.com/items/',
  cache: {
    key: 'GET/my-api.com/items',
    strategy: api.cache
      .get(api.constants.CACHE_TYPES.TTL_SUCCESS)
      .buildStrategy({ ttl: 600000 }), // 10 minutes
  },
});
  • shouldFetch function - a custom function to defined when resource valid:
api.actions.invoke({
  method: 'GET',
  headers: { Accept: 'application/json' },
  endpoint: 'https://my-api.com/items/',
  cache: {
    key: 'GET/my-api.com/items',
    shouldFetch({ state: CachedApiState }) {
      // Define your logic when the resource should be re-fetched
      return true;
    },
  },
});

* Check getResult selector docs for CachedApiState structure.

invalidateCache()

If you're restoring redux state from offline storage, there might be some interrupted fetch requests - which can restore your app in a broken state. You can invalidate all the cached redux state, or selectively with cacheKey.

dispatch(api.actions.invalidateCache(
  cacheKey: ?string // unique cache key
));

clearCache()

Clear all the cached redux state, or selectively with cacheKey.

dispatch(api.actions.clearCache(
  cacheKey: ?string // unique cache key
));

Redux Selectors

getResult()

Select all information about API request.

const response: ?CachedApiState = api.selectors.getResult(
  state: Object, // redux state
  cacheKey: string // unique cache key
);

The selected CachedApiState object has a structure of:

{
  fetching: boolean, // is fetching in progress
  fetched: boolean, // was any fetch completed
  error: boolean, // was last response an error
  timestamp: ?number, // last response timestamp
  successPayload: ?any, // last success response payload
  errorPayload: ?any, // last error response payload
}

* If getResult response is undefined it means the API request wasn't initialized yet.

Caching Strategies

  • SIMPLE_SUCCESS - uses previous successful fetch result
const strategy = api.cache
  .get(api.constants.CACHE_TYPES.SIMPLE_SUCCESS)
  .buildStrategy();
  • SIMPLE - uses any previous payload fetch result
const strategy = api.cache
  .get(api.constants.CACHE_TYPES.SIMPLE)
  .buildStrategy();
  • TTL_SUCCESS - uses previous successful fetch result if time to live (TTL) was not reached
const strategy = api.cache
  .get(api.constants.CACHE_TYPES.TTL_SUCCESS)
  .buildStrategy({ ttl: 1000 });
  • TTL - uses any previous fetch result if TTL was not reached
const strategy = api.cache
  .get(api.constants.CACHE_TYPES.TTL)
  .buildStrategy({ ttl: 1000 });

Demos

Other Solutions

There are other solutions if redux-cached-api-middleware doesn't fit your needs:

References

License

MIT

You can’t perform that action at this time.