Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save konstantin24121/27230593930e0678f3639fb7077d4765 to your computer and use it in GitHub Desktop.
Save konstantin24121/27230593930e0678f3639fb7077d4765 to your computer and use it in GitHub Desktop.
Sync redux store with localStorage
/**
* Synchronization store with localStorage
* Allows to take the default states from localStorage
* @param {strign} name reducer's name preferable
* @param {object} defaultInitial reducer's default state
* @return {object} initial state and methods
*/
export default (name, defaultInitial) => {
const localStorageState = localStorage.getItem(`yakutia.${name}`);
const mergedState = localStorageState && JSON.parse(localStorageState);
let initialState;
if (mergedState) {
initialState = {
...defaultInitial,
...mergedState.state,
};
} else {
initialState = defaultInitial;
}
let syncedKeys = null;
/**
* Sync reducer with localStorage
*/
const sync = (getState) => {
const newState = getState();
let syncedState = newState;
if (syncedKeys) {
syncedState = {};
for (let i = 0; i < syncedKeys.length; i += 1) {
const key = syncedKeys[i];
syncedState[key] = newState[key];
}
}
localStorage.setItem(
`yakutia.${name}`,
JSON.stringify({ state: syncedState, _hash: Date.now() })
);
return newState;
};
/**
* Set keys for sync
* @param {Array} keys
* @return {void}
*/
const syncOnlyKeys = (keys) => {
syncedKeys = keys;
};
const discardListData = (key, callback) => {
initialState[key] = initialState[key].map(callback);
};
return { initialState, sync, syncOnlyKeys, discardListData };
};
import createLocalStorageStoreSyncer from '../localStorageStoreSync';
describe('Sync store with localStorage', () => {
beforeEach(() => {
localStorage.clear();
});
it('correct initialState', () => {
localStorage.setItem('test', JSON.stringify(
{
state: {
happyShitting: 'wat',
},
_hash: Date.now(),
}
));
const defaultInitial = {
ass: true,
};
const storageSyncer = createLocalStorageStoreSyncer('test', defaultInitial);
expect(storageSyncer.initialState).toEqual({
happyShitting: 'wat',
ass: true,
});
});
it('correct sync with store', () => {
const storageSyncer = createLocalStorageStoreSyncer('test', {});
storageSyncer.sync(() => {
return {
youKnowNothing: 'John Show',
};
});
const state = JSON.parse(localStorage.getItem('test')).state;
expect(state).toEqual({
youKnowNothing: 'John Show',
});
});
it('sync only some keys with store', () => {
const storageSyncer = createLocalStorageStoreSyncer('test', {});
storageSyncer.syncOnlyKeys(['tormund', 'hound']);
storageSyncer.sync(() => {
return {
igrit: 'John Show',
tormund: 'Happy shitting',
hound: 'Fuck the king',
};
});
const state = JSON.parse(localStorage.getItem('test')).state;
expect(state).toEqual({
tormund: 'Happy shitting',
hound: 'Fuck the king',
});
});
});
import { localStorageStoreSync as localStorageStoreSyncer,
deleteFromList, changeItemInList } from 'common/utils';
import TYPES from './types';
import { hypothesisScheme } from './schemes';
const defaultInitial = {
list: [],
data: [],
};
// Create storeSyncer with key 'hypothesises' equals reducer name
const localStorageSyncer = localStorageStoreSyncer(
'hypothesises',
defaultInitial,
);
// Will be sync only a 'list' key
localStorageSyncer.syncOnlyKeys([
'list',
]);
// Default meta-data for hypothesises
const defaultHypothesis = {
isLoading: false,
isFetched: false,
isApproved: false,
isRejected: false,
isDisabled: false,
};
// Clean meta-data of each list item in localStorage
localStorageSyncer.discardListData('list', (item) => ({
...item,
isFetched: false,
isLoading: false,
}));
export default function (state = localStorageSyncer.initialState, { type, payload }) {
return localStorageSyncer.sync(() => {
switch (type) {
case TYPES.register: {
const newHypotesisList = [...state.list];
const existedHypothesis = newHypotesisList.find(
(item) => (payload.id && item.id === payload.id) || item.segment === payload.segment
);
if (!existedHypothesis) {
newHypotesisList.push({ ...payload, ...defaultHypothesis });
}
const newHypotesisDataList = [...state.data, { ...payload }];
return { ...state, list: newHypotesisList, data: newHypotesisDataList };
}
case TYPES.fetching: {
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id) || item.segment === payload.segment,
(hypothesis) => ({
...hypothesis,
isLoading: true,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.fetchReject: {
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id) || item.segment === payload.segment,
(hypothesis) => ({
...hypothesis,
isLoading: false,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.fetchSuccess: {
const { id } = payload.response;
const newHypotesisList = changeItemInList(
state.list,
(item) => (id && item.id === id) || item.segment === payload.segment,
(hypothesis) => ({
...hypothesis,
id,
isLoading: false,
isFetched: true,
})
);
const newHypotesisDataList = changeItemInList(
state.data,
(item) => item.id === payload.response.id || item.segment === payload.segment,
(hypothesis) => ({
...hypothesis,
...hypothesisScheme(payload.response),
})
);
return { ...state, list: newHypotesisList, data: newHypotesisDataList };
}
case TYPES.hypothesisRemove: {
const newHypotesisList = deleteFromList(
state.list,
({ segment, id }) => (id === payload.id || segment === payload.segment)
);
const newDataHypotesisList = deleteFromList(
state.data,
({ segment, id }) => (id === payload.id || segment === payload.segment)
);
return { ...state, list: newHypotesisList, data: newDataHypotesisList };
}
case TYPES.hypothesisResete: {
const newHypotesisList = changeItemInList(
state.list,
(item) => item.segment === payload.segment,
(hypothesis) => ({
...hypothesis,
disableExpired: null,
isApproved: false,
isRejected: false,
isDisabled: false,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.hypothesisApproved: {
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id),
(hypothesis) => ({
...hypothesis,
isApproved: true,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.hypothesisDisabled: {
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id),
(hypothesis) => ({
...hypothesis,
isDisabled: true,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.hypothesisRejected: {
let expired = 0;
if (payload.repeatDays) {
const date = new Date();
date.setTime(date.getTime() + (payload.repeatDays * 3600 * 24 * 1000));
expired = date.getTime();
}
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id),
(hypothesis) => ({
...hypothesis,
isRejected: true,
isDisabled: true,
disableExpired: expired,
})
);
return { ...state, list: newHypotesisList };
}
case TYPES.hypothesisSetExpiredTime: {
const date = new Date();
date.setTime(date.getTime() + payload.expired);
const expired = date.getTime();
const newHypotesisList = changeItemInList(
state.list,
(item) => (payload.id && item.id === payload.id),
(hypothesis) => ({
...hypothesis,
disableExpired: expired,
})
);
return { ...state, list: newHypotesisList };
}
default:
return state;
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment