From 2de04ba677a62d31643e148045ff646f5befab0f Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Tue, 16 Aug 2022 14:02:43 +0900 Subject: [PATCH] feat: resync state --- README.md | 2 ++ src/constants.ts | 1 + src/persistReducer.ts | 14 ++++++++ src/persistStore.ts | 11 +++++- src/types.ts | 1 + tests/resync.spec.js | 79 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 tests/resync.spec.js diff --git a/README.md b/README.md index 9a963053..0d699ab5 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,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.ts b/src/constants.ts index e76fce26..7249e5c8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,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.ts b/src/persistReducer.ts index 72023a1a..12ec0a20 100644 --- a/src/persistReducer.ts +++ b/src/persistReducer.ts @@ -10,6 +10,7 @@ import { PURGE, REHYDRATE, DEFAULT_VERSION, + RESYNC } from './constants' import type { @@ -159,6 +160,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.ts b/src/persistStore.ts index a7fc87eb..069b7bd8 100644 --- a/src/persistStore.ts +++ b/src/persistStore.ts @@ -6,7 +6,7 @@ import type { } from './types' import { AnyAction, createStore, Store } from 'redux' -import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from './constants' +import { FLUSH, PAUSE, PERSIST, RESYNC, PURGE, REGISTER, REHYDRATE, } from './constants' type BoostrappedCb = () => any; @@ -116,6 +116,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.ts b/src/types.ts index 8e83b67e..e51a2c0d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -148,6 +148,7 @@ export type PersistorSubscribeCallback = () => any; export interface Persistor { pause(): void; persist(): void; + resync(): Promise, purge(): Promise; flush(): Promise; dispatch(action: PersistorAction): PersistorAction; diff --git a/tests/resync.spec.js b/tests/resync.spec.js new file mode 100644 index 00000000..1c7b4a26 --- /dev/null +++ b/tests/resync.spec.js @@ -0,0 +1,79 @@ +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() + }) + }) +}) \ No newline at end of file