diff --git a/README.md b/README.md index 88b91459f..4ac7d46d3 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ const App = () => { - pauses persistence - `.persist()` - resumes persistence + - `.resync()` + - overrides redux state with the value in storage ## State Reconciler State reconcilers define how incoming state is merged in with initial state. It is critical to choose the right state reconciler for your state. There are three options that ship out of the box, let's look at how each operates: diff --git a/src/constants.js b/src/constants.js index 009705f10..83a08fcdd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -3,6 +3,7 @@ export const KEY_PREFIX = 'persist:' export const FLUSH = 'persist/FLUSH' export const REHYDRATE = 'persist/REHYDRATE' +export const RESYNC = 'persist/RESYNC' export const PAUSE = 'persist/PAUSE' export const PERSIST = 'persist/PERSIST' export const PURGE = 'persist/PURGE' diff --git a/src/persistReducer.js b/src/persistReducer.js index cf8d980f7..c2c70bd85 100644 --- a/src/persistReducer.js +++ b/src/persistReducer.js @@ -6,6 +6,7 @@ import { PURGE, REHYDRATE, DEFAULT_VERSION, + RESYNC, } from './constants' import type { @@ -155,6 +156,19 @@ export default function persistReducer( ...baseReducer(restState, action), _persist, } + } else if (action.type === RESYNC) { + getStoredState(config) + .then( + restoredState => + action.rehydrate(config.key, restoredState, undefined), + err => action.rehydrate(config.key, undefined, err) + ) + .then(() => action.result()) + + return { + ...baseReducer(restState, action), + _persist: { version, rehydrated: false }, + } } else if (action.type === FLUSH) { action.result(_persistoid && _persistoid.flush()) return { diff --git a/src/persistStore.js b/src/persistStore.js index f12675347..171981a9d 100644 --- a/src/persistStore.js +++ b/src/persistStore.js @@ -11,7 +11,7 @@ import type { } from './types' import { createStore } from 'redux' -import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from './constants' +import { FLUSH, PAUSE, PERSIST, RESYNC, PURGE, REGISTER, REHYDRATE } from './constants' type PendingRehydrate = [Object, RehydrateErrorType, PersistConfig] type Persist = (PersistConfig, MigrationManifest) => R => R @@ -120,6 +120,15 @@ export default function persistStore( persist: () => { store.dispatch({ type: PERSIST, register, rehydrate }) }, + resync: () => { + return new Promise(resolve => { + store.dispatch({ + type: RESYNC, + rehydrate, + result: () => resolve(), + }) + }) + }, } if (!(options && options.manualPersist)){ diff --git a/src/types.js b/src/types.js index c50d3cd39..4b04c03d9 100644 --- a/src/types.js +++ b/src/types.js @@ -81,6 +81,7 @@ type PersistorSubscribeCallback = () => any export type Persistor = { pause: () => void, persist: () => void, + resync: () => Promise, purge: () => Promise, flush: () => Promise, +dispatch: PersistorAction => PersistorAction, diff --git a/tests/resync.spec.js b/tests/resync.spec.js new file mode 100644 index 000000000..84b20cb3a --- /dev/null +++ b/tests/resync.spec.js @@ -0,0 +1,81 @@ +// @flow + +import test from 'ava' + +import _ from 'lodash' +import { createStore } from 'redux' + +import getStoredState from '../src/getStoredState' +import persistReducer from '../src/persistReducer' +import persistStore from '../src/persistStore' +import { createMemoryStorage } from 'storage-memory' + +const initialState = { a: 0 } +const persistObj = { + version: 1, + rehydrated: true +}; + +let reducer = (state = initialState, { type }) => { + console.log('action', type) + if (type === 'INCREMENT') { + return _.mapValues(state, v => v + 1) + } + return state +} + +const memoryStorage = createMemoryStorage() + +const config = { + key: 'resync-reducer-test', + version: 1, + storage: memoryStorage, + debug: true, + throttle: 1000, +} + +test('state is resync from storage', t => { + return new Promise((resolve, reject) => { + let rootReducer = persistReducer(config, reducer) + const store = createStore(rootReducer) + + const persistor = persistStore(store, {}, async () => { + + // 1) Make sure redux-persist and storage are in the same state + + await persistor.flush(); + let storagePreModify = await getStoredState(config) + + const oldStorageState = { + ...initialState, + _persist: persistObj, + }; + t.deepEqual( + storagePreModify, + oldStorageState + ) + + // 2) Change the storage directly (so redux-persist won't notice it changed) + + const newStorageValue = { + a: 1, // override the value of a + _persist: JSON.stringify(persistObj), + } + await memoryStorage.setItem(`persist:${config.key}`, JSON.stringify(newStorageValue)); + let storagePostModify = await getStoredState(config) + + // 3) Call resync and make sure redux-persist state was overriden by storage content + + await persistor.resync(); + t.deepEqual( + storagePostModify, + { + a: 1, + _persist: persistObj, + } + ) + + resolve() + }) + }) +}) diff --git a/types/constants.d.ts b/types/constants.d.ts index 6a0e40389..fd63b87fd 100644 --- a/types/constants.d.ts +++ b/types/constants.d.ts @@ -2,6 +2,7 @@ declare module "redux-persist/es/constants" { const KEY_PREFIX: 'persist:'; const FLUSH: 'persist/FLUSH'; const REHYDRATE: 'persist/REHYDRATE'; + const RESYNC: 'persist/RESYNC'; const PAUSE: 'persist/PAUSE'; const PERSIST: 'persist/PERSIST'; const PURGE: 'persist/PURGE'; diff --git a/types/types.d.ts b/types/types.d.ts index b3733bc8b..e12f2ee93 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -137,6 +137,7 @@ declare module "redux-persist/es/types" { interface Persistor { pause(): void; persist(): void; + resync(): Promise; purge(): Promise; flush(): Promise; dispatch(action: PersistorAction): PersistorAction;