npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-cached-api-middleware

v0.3.0

Published

API caching solution for redux apps

Downloads

494

Readme

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