Skip to content

Instantly share code, notes, and snippets.

@mattiaerre
Created April 29, 2020 02:12
Show Gist options
  • Save mattiaerre/8dbd2d8efca3f242c7085a9ce82ecbde to your computer and use it in GitHub Desktop.
Save mattiaerre/8dbd2d8efca3f242c7085a9ce82ecbde to your computer and use it in GitHub Desktop.
React hook useReducerWithLocalStorage
import { useState } from 'react';
// credit: https://usehooks.com/useLocalStorage/
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
function setValue(value) {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
}
return [storedValue, setValue];
}
export default useLocalStorage;
import { useReducer } from 'react';
import useLocalStorage from './useLocalStorage';
function useReducerWithLocalStorage({ initializerArg, key, reducer }) {
const [localStorageState, setLocalStorageState] = useLocalStorage(
key,
initializerArg
);
return useReducer(
(state, action) => {
const newState = reducer(state, action);
setLocalStorageState(newState);
return newState;
},
{ ...localStorageState }
);
}
export default useReducerWithLocalStorage;
import { renderHook, act } from '@testing-library/react-hooks';
import useLocalStorage from './useLocalStorage';
import useReducerWithLocalStorage from './useReducerWithLocalStorage';
jest.mock('./useLocalStorage');
const emptyState = { firstName: '', lastName: '' };
const mockSetLocalStorageState = jest.fn();
const mockLocalStorageState = { ...emptyState, lastName: 'Doe' };
useLocalStorage.mockImplementation(() => [
mockLocalStorageState,
mockSetLocalStorageState
]);
const REACT_APP_STATE = 'REACT_APP_STATE';
const CHANGE_FIRST_NAME = 'CHANGE_FIRST_NAME';
function reducer(state, action) {
switch (action.type) {
case CHANGE_FIRST_NAME:
return { ...state, firstName: action.value };
default:
return state;
}
}
const mockReducer = jest.fn((state, action) => reducer(state, action));
test('useReducerWithLocalStorage', () => {
const {
result: {
current: [state, dispatch]
}
} = renderHook(() =>
useReducerWithLocalStorage({
initializerArg: emptyState,
key: REACT_APP_STATE,
reducer: mockReducer
})
);
expect(useLocalStorage).toBeCalledWith(REACT_APP_STATE, emptyState);
expect(state).toEqual(mockLocalStorageState);
act(() => {
dispatch({ type: CHANGE_FIRST_NAME, value: 'John' });
});
expect(mockSetLocalStorageState).toBeCalledWith({
firstName: 'John',
lastName: 'Doe'
});
});
@mattiaerre
Copy link
Author

mattiaerre commented Apr 30, 2020

this is awesome @mdboop thanks for that. You are absolutely right. I like very much your suggestion and

I think you would have to introduce a new argument, something like defaultOverrides or something, because you couldn't simply merge the states (you would end up writing over every key).

that or

One other option is to add an additional feature to this reducer to pass a list of properties I do not wish to store locally

in this case, the defaultOverrides will be inferred by the initialState.

also, I've created a repo for this component as well as published it to npm:

@mattiaerre
Copy link
Author

how about this?

import { useReducer } from 'react';
import remove from './remove';
import useLocalStorage from './useLocalStorage';

function useReducerWithLocalStorage({
  blacklist = [],
  initializerArg,
  key,
  reducer
}) {
  const [localStorageState, setLocalStorageState] = useLocalStorage(
    key,
    remove({ blacklist, state: initializerArg })
  );

  return useReducer(
    (state, action) => {
      const newState = reducer(state, action);
      setLocalStorageState(remove({ blacklist, state: newState }));
      return newState;
    },
    { ...initializerArg, ...localStorageState }
  );
}

export default useReducerWithLocalStorage;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment