Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement caching in Redux?

I would like to avoid calling an API twice if I already have the data in my store.

How do I do this with Redux?

like image 201
ng2user Avatar asked Mar 23 '17 15:03

ng2user


People also ask

Can Redux be used for caching?

When data is fetched from the server, RTK Query will store the data in the Redux store as a 'cache'. When an additional request is performed for the same data, RTK Query will provide the existing cached data rather than sending an additional request to the server.

How do you cache data in React JS?

Approach: Follow these simple steps in order to store multiple cache data in ReactJS. We have created our addMultipleCacheData function which takes the user data list and store into the browser cache. When we click on the button, the function is triggered and data gets stored into the cache, and we see an alert popup.

How do I request http cache?

The following cache request directives can be used by the client in its HTTP request: S.N. A cache must not use the response to satisfy a subsequent request without successful revalidation with the origin server. The cache should not store anything about the client request or server response.


2 Answers

The ideal solution to this in my opinion is to use Reselect selectors (https://github.com/reactjs/reselect). Here is a contrived example:

import { createSelector } from 'reselect';  const getObjs = state => state.objs; const currentObjId = state => state.currentObjId;  export const getObj = createSelector(   [ currentObjId, getObjs ],   (id, objs) => objs.get(href) ); 

Used like this:

import { getObj } from './selectors';  const ExampleComponent = ({obj}) => <div>{ obj.name }</div>;  const mapStateToProps = state => ({   obj: getObj(state) });  export default connect(mapStateToProps)(ExampleComponent); 

The first time you run this, one obj based on some id (also in the state) will be "selected" from the list of all objs. The next time, if the inputs have not changed (look at reselect documentation for the definition of equivalence) it will simply return the computed value from last time.

You also have the option to plug-in a different type of cache, e.g. LRU. That's a bit more advanced, but very doable.

The major advantage of Reselect is that it allows you to cleanly optimise without manually maintaining extra state in redux that you would then have to remember to update if a change to the original data was made. Timo's answer is good, but I would argue that the weakness is that it doesn't cache expensive client side computation (I know this wasn't the exact question, but this answer is about best practice redux caching in general, applied to your problem), only fetching. You can do something very similar to what Timo suggests, but incorporate reselect for a very tidy solution. In an action creator you could have something like this:

export const fetchObj = (dispatch, getState) => {   if (hasObj(getState())) {     return Promise.resolve();   }    return fetchSomething()     .then(data => {       dispatch(receiveObj(data));       return data;     }); }; 

You would have a selector specifically for hasObj potentially based on the above selector (I do so here specifically to show how you can compose selectors easily), like:

export const hasObj = createSelector(   [ getObj ],   obj => !!obj ); 

Once you start using this to interface with redux, it starts to make sense to use it exclusively in mapStateToProps even for simple selects so that at a future time, if the way that state is computed changes, you do not need to modify all of the components which use that state. An example of this might be something like having an array of TODOs when is used to render a list in several different components. Later in your application development process you realise you want to filter that list of TODOs by default to only ones that are incomplete. You change the selector in one place and you are done.

like image 71
dpwr Avatar answered Sep 23 '22 13:09

dpwr


I'm assuming you are using async actions to handle your API calls.

This is the place where I would put the caching logic, which results in a nice encapsulation:

export function fetchData(url) {        return function (dispatch) {         dispatch(requestData(url))          if (isCached(url)) {             const cachedData = getCached(url)             dispatch(receiveData(url, cachedData))         } else {             return fetch(url)               .then(response => response.json())               .then(json => {                   dispatch(receiveData(url, json))                   setCached(url, json)               })         }     } } 

Implementing isCached, getCached and setCached for your local storage should be rather straightforward.

like image 42
TimoStaudinger Avatar answered Sep 20 '22 13:09

TimoStaudinger