DEV Community

Add Offline-Caching to Your React Reducer with 5 Lines of Code

Rakan Nimer on August 01, 2019

When building web applications I often use this method to add local storage caching of my state. Suppose we're starting with : const [state, d...
Collapse
 
rakannimer profile image
Rakan Nimer • Edited

What happens when more than one reducer stores data under the my-state key?

Very weird stuff ! It's not at all recommended to do that.

What happens when one wants to upgrade the structure of of the store between deployments?

It's recommended to change the state key when changing the structure of the store (e.g. my-state-1, my-state-2)

What happens when the store contains a Map or an instantiated class or a function?

This technique as is, requires the state to be serializable, to handle special cases you would need to add custom parsing and stringifying for non-serializable variables or simply ignore them when reading and writing to the the cache.

 
rakannimer profile image
Rakan Nimer • Edited

Thanks for taking the time to write that !

The main reason I didn't go into it, is to keep the code under 5 lines 😅

I wanted the reader to quickly understand the idea of using HOFs with the reducer and build on it and experiment themselves.

Now they can also read/use this hook 👌

Thread Thread
 
bionicles profile image
bion howard • Edited
// src/state/hook.jsx

import React, { createContext, useContext, useReducer, useMemo } from "react";
import { reduce, logger, initialState } from "state";

const LOGGING = 1;

export const StateContext = createContext();

export const withCache = reducer => (s, a) => {
  const newS = reducer(s, a);
  localStorage.setItem("state", JSON.stringify(newS));
  return newS;
};

export const StateProvider = ({ children }) => {
  const [state, dispatch] = useReducer(
    withCache(LOGGING ? logger : reduce),
    JSON.parse(localStorage.getItem("state")) || initialState
  );
  const value = useMemo(() => {
    return { state, dispatch };
  }, [state, dispatch]);
  return (
    <StateContext.Provider value={value}>{children}</StateContext.Provider>
  );
};

export const useState = () => useContext(StateContext);
// src/app.js
import { StateProvider } from "state";

... layout stuff ...

const App = () => (
  <StateProvider>
    <Layout />
  </StateProvider>
);

export default App;
// src/components/my-component.jsx
import React from "react";
import { useState } from "state";

const MyComponent = props => {
    const { state: { thingy }, dispatch } = useState();
    return // my jsx
}
// src/state/index.js
export * from "state/initial-state";
export * from "state/reduce";
export * from "state/logger";
export * from "state/hook";