rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.94k stars 866 forks source link

Issue with reconcilate with INITIAL_STATE and local persisted store #1357

Open fernando-tiburcio opened 2 years ago

fernando-tiburcio commented 2 years ago

Hi Guys, why when I add a new property on my Redux INITIAL_STATE, this new property is just droped on REHYDRATE step of Redux Persist instead of being merged with local state?

I already tried to configure the modes autoMergeLevel1 and autoMergeLevel2 in the stateReconciler of the persistReducer config but without success.

Here is my store.js

import Constants from 'expo-constants';
import * as Sentry from '@sentry/react';
import { toastReducer as toast } from 'react-native-redux-toast';
import {
  applyMiddleware, combineReducers, compose, createStore,
} from 'redux';
import { resettableReducer } from 'reduxsauce';
import { composeWithDevTools } from 'redux-devtools-extension';
import {
  consumeActionMiddleware,
  offlineMiddleware as libOfflineMiddleware,
  reducer as offlineReducer,
  suspendSaga,
} from 'redux-offline-queue';
import { createTransform, persistReducer, persistStore } from 'redux-persist';
import createSagaMiddleware from 'redux-saga';

import { configPersist } from '@agriness-mobile/arca-business/dist/store';

import { convertStringToUtcMoment, stringifyDateTime } from '~/helpers';
import { RESET_STORE_LOCAL, RESET_STORE_MIRROR } from '~/constants';
import {
  AuthReducers, DownloadStatusReducers, LocalReducers, MirrorReducers,
} from '~/reducers';

import {
  AuthLifecycleMiddleware,
  FilterUnusedActionsMiddleware,
  LoginGateKeeperMiddleware,
} from './middlewares';
import rootSaga from './Sagas';

const createDateTransform = (config) => createTransform(
  stringifyDateTime,
  convertStringToUtcMoment,
  config,
);

const migrations = {};
const configStore = (callback = null, reduxSagasErrorHandler = null) => {
  const { releaseChannel } = Constants.manifest;

  /*
    Sentry https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/redux/
  */

  const optionsSentryRedux = {
    stateTransformer: !releaseChannel
      ? (_) => _
      : (state) => {
        const { _persist: persistLocal, ...local } = state.local;
        const { _persist: persistDownloadStatus, ...downloadStatus } = state.downloadStatus;
        return {
          downloadStatus,
          farms: state.mirror.farms,
          local,
          username: state.auth?.username,
        };
      },
  };

  const sentryReduxEnhancer = Sentry.createReduxEnhancer(optionsSentryRedux);

  /*
    Add LoginReducer persistence configs (using expo-secure-store lib) with a transform for
    accessTokenExpirationDate (String -> Date).
  */

  const sagaMiddleware = createSagaMiddleware({ onError: reduxSagasErrorHandler });

  const middlewares = [];
  middlewares.push(libOfflineMiddleware());
  middlewares.push(suspendSaga(sagaMiddleware));
  middlewares.push(FilterUnusedActionsMiddleware);
  middlewares.push(LoginGateKeeperMiddleware);
  middlewares.push(AuthLifecycleMiddleware);
  middlewares.push(consumeActionMiddleware());

  const resettableLocal = resettableReducer(RESET_STORE_LOCAL);
  const resettableMirror = resettableReducer(RESET_STORE_MIRROR);

  const appReducers = combineReducers({
    auth: persistReducer(configPersist.authPersistConfig, AuthReducers),
    downloadStatus: persistReducer(
      configPersist.downloadStatusPersistConfig,
      resettableMirror(DownloadStatusReducers),
    ),
    local: resettableLocal(LocalReducers),
    mirror: persistReducer(configPersist.mirrorPersistConfig, resettableMirror(MirrorReducers)),
    offline: persistReducer(configPersist.offlinePersistConfig, resettableLocal(offlineReducer)),
    toast,
  });

  const middlewaresApplied = applyMiddleware(...middlewares);

  let enhancers;

  if (!releaseChannel) {
    const reduxDevToolsOptions = {};
    enhancers = composeWithDevTools(reduxDevToolsOptions)(middlewaresApplied);
  } else {
    enhancers = middlewaresApplied;
  }

  const persistedReducer = persistReducer(configPersist.persistConfigSecondLevel(migrations), appReducers);

  const store = createStore(persistedReducer, compose(enhancers, sentryReduxEnhancer));
  const persistor = persistStore(store, null, callback);

  sagaMiddleware.run(rootSaga, persistor);

  return { persistor, store };
};

export default { configStore, createDateTransform };

My reducer :

import { createReducer } from 'reduxsauce';

import FarmHandlers from './Farm/Mirror';
import RecommendationsHandlers from './Recommendations/Mirror';

export const INITIAL_STATE = {
  currentRecommendationEvidence: [],
  farms: [],
};

const HANDLERS = {
  ...FarmHandlers,
  ...RecommendationsHandlers,
};

export default createReducer(INITIAL_STATE, HANDLERS);

And my configPersist :

import Constants from 'expo-constants';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createMigrate, createTransform } from 'redux-persist';

import { convertStringToUtcMoment, stringifyDateTime } from '~/helpers';

const createDateTransform = config => createTransform(stringifyDateTime, convertStringToUtcMoment, config);

const { releaseChannel } = Constants.manifest;

const persistConfigSecondLevel = migrations => ({
  blacklist: ['offline', 'auth'],
  debug: !releaseChannel,
  key: 'root',
  migrate: createMigrate(migrations, { debug: !releaseChannel }),
  storage: AsyncStorage,
  timeout: 0,
  version: 2,
  whitelist: ['mirror', 'local', 'notifications'],
});

const downloadStatusPersistConfig = {
  key: 'downloadStatus',
  storage: AsyncStorage,
  timeout: 0,
};

const mirrorPersistConfig = {
  key: 'mirror',
  storage: AsyncStorage,
  timeout: 0,
  whitelist: ['farms'],
};

const authPersistConfig = {
  blacklist: ['authRequestLoading', 'failureMessage'],
  key: 'auth',
  storage: AsyncStorage,
  timeout: 0,
  transforms: [createDateTransform({ whitelist: ['accessTokenExpirationDate', 'refreshTokenExpirationDate'] })],
};

const notificationsPersistConfig = {
  key: 'notifications',
  storage: AsyncStorage,
  timeout: 0,
};

const offlinePersistConfig = {
  blacklist: ['isConnected'],
  key: 'offline',
  storage: AsyncStorage,
  timeout: 0,
};

export {
  authPersistConfig,
  downloadStatusPersistConfig,
  mirrorPersistConfig,
  notificationsPersistConfig,
  offlinePersistConfig,
  persistConfigSecondLevel,
};
MohammadAzimi commented 2 years ago

@fernando-tiburcio any workaround?

fernando-tiburcio commented 2 years ago

@fernando-tiburcio any workaround?

Yes, I fixed this using a new migration and incrementing the version of the persisted state, this solve my problem... code below.

const rootMigrations = {
  3: (state) => ({
      ...state,
      mirror: {
        ...state.mirror,
        currentRecommendationEvidence: [],
      },
    })
};
const persistConfigSecondLevel = migrations => ({
    blacklist: ['offline', 'auth'],
    debug: !releaseChannel,
    key: 'root',
    migrate: createMigrate(migrations, { debug: !releaseChannel }),
    storage: AsyncStorage,
    timeout: 0,
    version: 3,
    whitelist: ['mirror', 'local', 'notifications'],
  });