Last active
May 1, 2018 08:02
-
-
Save konstantin24121/27230593930e0678f3639fb7077d4765 to your computer and use it in GitHub Desktop.
Sync redux store with localStorage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 }; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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