redux-persist icon indicating copy to clipboard operation
redux-persist copied to clipboard

[redux-persist]How to update persistReducer which is stored in localstorage

Open vidhyeshpatil opened this issue 4 years ago • 12 comments

What is the best way to update persistStore when my store / reducer is updated with new key, value (inside initial state) or a new reducer.

The problem which I am facing write now, is my persistStore is not getting updated when I update my reducer with additional value.

eg: Reducer A Old one
const initialState = { number: 0 }

Updated one const initialState = { number: 0, checkPage: [] }

When my application runs again, it tries to access checkPage array it throws an error "Cannot read property undefined of undefined". Which means my persistedReducer was not updated.

A common use case, Can you suggest the best way to resolve the issue ?

vidhyeshpatil avatar May 26 '20 16:05 vidhyeshpatil

Hi @vidhyeshpatil!

You need to create a migration and pass it to your persistConfig.

Here is an example:

import rootReducer from './reducers';

const migrations = {
  0: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          colorPalette: 'DEFAULT',
        }
      }
    }
  },
};

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  version: 0,
  timeout: 0,
  migrate: createMigrate(migrations, { debug: true }),
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export default () => {
  const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));;
  const persistor = persistStore(store);
  return { store, persistor };
}

As you can see I created a migrations object and gave it the key 0 which I then matched to the version in persistConfig.

When your app runs again it will check for the key 0 and return the state that is returned by the key 0 in migrations. In other words: whatever you return from 0 is your new state. In your case it would be something like:

const migrations = {
  0: (state) => {
    return {
      ...state,
      reducerA: {
        ...state.reducerA, // number: 0
       checkPage: [],
      }
    }
  },
};

In the future, you would increment this number by one and on app start redux persist will check if its bigger than the current version and if it is it will migrate the new state.

This is currently my migrations object (so you get the idea):

import moment from 'moment';
import strings from './i18n/strings';
import ReduxThunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer, createMigrate } from 'redux-persist';
import { defaultActivities, ADDED_IN_VERSION_3_4 } from './assets/activities/activities';
import { INITIAL_HOME_WIDGETS, INITIAL_MENU_ITEMS, LINE_CHART_CURVE_TYPES, TIME_WINDOWS } from './constants';
import { EXTENDED_EMOTIONS_MAP_AS_ARRAY, EXTENDED_EMOTIONS_MAP, ALL_EMOTIONS, BASIC_EMOTIONS } from './assets/objectProperties/emotionsMap';

import rootReducer from './reducers';

const migrations = {
  0: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          colorPalette: 'DEFAULT',
        }
      }
    }
  },
  1: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          moodLayout: 'GRID', 
          firstMonthToShow: 1,
          shouldHighlightCurrentDay: true, 
        },
        moodSelectionCategory: {
          defaultAndCustomMoodsAsSingleArray: EXTENDED_EMOTIONS_MAP_AS_ARRAY, 
          defaultAndCustomMoodsAsSegmentedObject: EXTENDED_EMOTIONS_MAP
        }
      }
    }
  },
  2: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          ...state.settings.moodColorCategory,
          shouldUseDefaultColorPalettes: true,
          customMoodColors_1: null,
          customMoodColors_2: null,
          customMoodColors_3: null,
          customMoodSelected: null, 
        },
        languagePreference: {
          userEnforcedLanguage: null,
        },
        whatsNew: {
          showWhatsNewModal: true,
        }
      }
    }
  },
  3: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        backgroundCategory: { 
          isBackgroundUsingVideo: true,
          savedImage: null,
          savedVideo: 'defaultVideo',
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  4: (state) => {
    return {
      ...state,
      challenges: [],
      pastChallenges: [],
      googleDrive: {
        userInfo: null,
        lastBackupTimestamp: null,
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  5: (state) => {
    return {
      ...state,
      userInfo: {
        ...state.userInfo,
        lastViewedRewardedVideo: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  6: (state) => {
    return {
      ...state,
      activities: defaultActivities,
      pinlock: {
        pin: '',
        pinlockEnabled: false,
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  7: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingButton: true,
          shouldAllowWhiteBorders: false, 
          shouldAllowGradients: true, 
          firstMonthToShow: 1,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  8: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowWhiteBorders: false,
          shouldAllowGradients: true,
          firstMonthToShow: 1,
          shouldAllowActivities: true,
          shouldAllowLocation: true,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
      // open street maps
      locationOSM: {
        locationName: null,
        openStreetMapId: null,
        latitude: 85.287973,
        longitude: 36.198180,
        timestamp: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      // google maps
      locationGM: {
        cachedPredictions: [],
        timestamp: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      // custom location
      locationCL: {
        cachedCustomLocations: [],
      }
    }
  },
  9: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        backgroundCategory: {
          ...state.settings.backgroundCategory,
          isBackgroundImageCustom: false,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  },
  10: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingButton: true,
          shouldAllowDotStyle: false,
          dotStylePosition: 'right',
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
        migration: {
          hasMigratedToMultipleEntries: false,
        }
      },
    }
  },
  11: (state) => {
    return {
      ...state,
      extraNotifications: [],
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingEntryButton: true,
        },
        ratingDescribers: {
          happyMood: strings.EmotionsLayoutDescribers.basicMoods.happyMood,
          contentMood: strings.EmotionsLayoutDescribers.basicMoods.contentMood,
          neutralMood: strings.EmotionsLayoutDescribers.basicMoods.neutralMood,
          sadMood: strings.EmotionsLayoutDescribers.basicMoods.sadMood,
          depressedMood: strings.EmotionsLayoutDescribers.basicMoods.depressedMood,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  },
  12: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          weekStartsOnMonday: true,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  }, 
  13: (state) => {
    return {
      ...state,
      inAppReviews: {
        rating: null,
        feedback: '',
        timestamp: null,
      },
      firebaseBackup: {
        lastBackupTimestamp: null,
      },
      imageBackupTracker: {
        toUpload: {},
        toDelete: [],
      },
      userInfo: {
        ...state.userInfo,
        birthday: null,
      },
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowPhotos: true,
          shouldAllowCalendarFullWidth: false,
          googleColorScheme: 'Standard',
        },
        notificationCategory: {
          ...state.settings.notificationCategory,
          reminderNotifMinute: 0,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
        tipsAndTricks: {
          hasSeenPhotosMessage: false,
        },
      },
      map: {
        initialRegion: null,
      },
      autoSaveEntry: {
        entryText: '',
        timestamp: new Date().getTime(),
      },
      routines: {
        activeRoutines: [],
        routineProgress: {},
        deletedRoutines: [],
        customRoutineItems: [],
      },
      subscriptions: {
        purchases: {},
        lastValidationCheck: null,
        isSubscriptionActive: false,
        latestPurchasedProduct: null,
      },
      activities: [
        ...state.activities,
        ...ADDED_IN_VERSION_3_4,
      ]
    }
  },
  14: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        weather: {
          allowWeather: true,
          unitOfMeasurement: 'metric', // us_custom
        },
        tipsAndTricks: {
          ...state.settings.tipsAndTricks,
          hasSeenMultipleEntriesMessage: false,
        }
      },
    }
  },
  15: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        tipsAndTricks: {
          ...state.settings.tipsAndTricks,
          hasSeenMonthStatisticMessage: false,
        },
        hapticFeedback: {
          allowHapticFeedback: false,
        },
      },
    }
  },
  16: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          currentActiveJournalTab: 'entry',
          backgroundOpacity: .72,
          elementBackgroundColor: 100,
          isShowingCurrentWeek: false,
          monthView: 'default',
          menuItems: [...INITIAL_MENU_ITEMS],
          homeComponentWidgets: [...INITIAL_HOME_WIDGETS],
        },
        migration: {
          ...state.migration,
          hasMigratedToAccurateTimestamps: false,
        },
        insightCategory: {
          ratingLineChartCurve: LINE_CHART_CURVE_TYPES[0], // step or average
          timeWindowToDisplay: TIME_WINDOWS[0],
        },
      },
      notifications: {},
      gratitudeJournal: {},
      quotes: {
        todaysQuote: {
          ...strings.Quotes[0],
          dateSet: new Date().getTime(),
        },
        likedQuotes: [],
      },
      emotions: {
        allEmotions: [...ALL_EMOTIONS],
        basicEmotions: [...BASIC_EMOTIONS],
      },
    };
  }
};

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  version: 16,
  timeout: 0,
  migrate: createMigrate(migrations, { debug: true }),
  writeFailHandler: error => console.log('ERROR PERSISTING DATA', error),
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export default () => {
  const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));;
  const persistor = persistStore(store);
  return { store, persistor };
}

wmonecke avatar May 27 '20 19:05 wmonecke

Hi @vidhyeshpatil!

You need to create a migration and pass it to your persistConfig.

Here is an example:

import rootReducer from './reducers';

const migrations = {
  0: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          colorPalette: 'DEFAULT',
        }
      }
    }
  },
};

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  version: 0,
  timeout: 0,
  migrate: createMigrate(migrations, { debug: true }),
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export default () => {
  const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));;
  const persistor = persistStore(store);
  return { store, persistor };
}

As you can see I created a migrations object and gave it the key 0 which I then matched to the version in persistConfig.

When your app runs again it will check for the key 0 and return the state that is returned by the key 0 in migrations. In other words: whatever you return from 0 is your new state. In your case it would be something like:

const migrations = {
  0: (state) => {
    return {
      ...state,
      reducerA: {
        ...state.reducerA, // number: 0
       checkPage: [],
      }
    }
  },
};

In the future, you would increment this number by one and on app start redux persist will check if its bigger than the current version and if it is it will migrate the new state.

This is currently my migrations object (so you get the idea):

import moment from 'moment';
import strings from './i18n/strings';
import ReduxThunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer, createMigrate } from 'redux-persist';
import { defaultActivities, ADDED_IN_VERSION_3_4 } from './assets/activities/activities';
import { INITIAL_HOME_WIDGETS, INITIAL_MENU_ITEMS, LINE_CHART_CURVE_TYPES, TIME_WINDOWS } from './constants';
import { EXTENDED_EMOTIONS_MAP_AS_ARRAY, EXTENDED_EMOTIONS_MAP, ALL_EMOTIONS, BASIC_EMOTIONS } from './assets/objectProperties/emotionsMap';

import rootReducer from './reducers';

const migrations = {
  0: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          colorPalette: 'DEFAULT',
        }
      }
    }
  },
  1: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          moodLayout: 'GRID', 
          firstMonthToShow: 1,
          shouldHighlightCurrentDay: true, 
        },
        moodSelectionCategory: {
          defaultAndCustomMoodsAsSingleArray: EXTENDED_EMOTIONS_MAP_AS_ARRAY, 
          defaultAndCustomMoodsAsSegmentedObject: EXTENDED_EMOTIONS_MAP
        }
      }
    }
  },
  2: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        moodColorCategory: {
          ...state.settings.moodColorCategory,
          shouldUseDefaultColorPalettes: true,
          customMoodColors_1: null,
          customMoodColors_2: null,
          customMoodColors_3: null,
          customMoodSelected: null, 
        },
        languagePreference: {
          userEnforcedLanguage: null,
        },
        whatsNew: {
          showWhatsNewModal: true,
        }
      }
    }
  },
  3: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        backgroundCategory: { 
          isBackgroundUsingVideo: true,
          savedImage: null,
          savedVideo: 'defaultVideo',
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  4: (state) => {
    return {
      ...state,
      challenges: [],
      pastChallenges: [],
      googleDrive: {
        userInfo: null,
        lastBackupTimestamp: null,
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  5: (state) => {
    return {
      ...state,
      userInfo: {
        ...state.userInfo,
        lastViewedRewardedVideo: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  6: (state) => {
    return {
      ...state,
      activities: defaultActivities,
      pinlock: {
        pin: '',
        pinlockEnabled: false,
      },
      settings: {
        ...state.settings,
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  7: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingButton: true,
          shouldAllowWhiteBorders: false, 
          shouldAllowGradients: true, 
          firstMonthToShow: 1,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      }
    }
  },
  8: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowWhiteBorders: false,
          shouldAllowGradients: true,
          firstMonthToShow: 1,
          shouldAllowActivities: true,
          shouldAllowLocation: true,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
      // open street maps
      locationOSM: {
        locationName: null,
        openStreetMapId: null,
        latitude: 85.287973,
        longitude: 36.198180,
        timestamp: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      // google maps
      locationGM: {
        cachedPredictions: [],
        timestamp: moment("January 20 2019 05:06:07", "MMMM DD YYYY hh:mm:ss"),
      },
      // custom location
      locationCL: {
        cachedCustomLocations: [],
      }
    }
  },
  9: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        backgroundCategory: {
          ...state.settings.backgroundCategory,
          isBackgroundImageCustom: false,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  },
  10: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingButton: true,
          shouldAllowDotStyle: false,
          dotStylePosition: 'right',
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
        migration: {
          hasMigratedToMultipleEntries: false,
        }
      },
    }
  },
  11: (state) => {
    return {
      ...state,
      extraNotifications: [],
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowFloatingEntryButton: true,
        },
        ratingDescribers: {
          happyMood: strings.EmotionsLayoutDescribers.basicMoods.happyMood,
          contentMood: strings.EmotionsLayoutDescribers.basicMoods.contentMood,
          neutralMood: strings.EmotionsLayoutDescribers.basicMoods.neutralMood,
          sadMood: strings.EmotionsLayoutDescribers.basicMoods.sadMood,
          depressedMood: strings.EmotionsLayoutDescribers.basicMoods.depressedMood,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  },
  12: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          weekStartsOnMonday: true,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
      },
    }
  }, 
  13: (state) => {
    return {
      ...state,
      inAppReviews: {
        rating: null,
        feedback: '',
        timestamp: null,
      },
      firebaseBackup: {
        lastBackupTimestamp: null,
      },
      imageBackupTracker: {
        toUpload: {},
        toDelete: [],
      },
      userInfo: {
        ...state.userInfo,
        birthday: null,
      },
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          shouldAllowPhotos: true,
          shouldAllowCalendarFullWidth: false,
          googleColorScheme: 'Standard',
        },
        notificationCategory: {
          ...state.settings.notificationCategory,
          reminderNotifMinute: 0,
        },
        whatsNew: {
          showWhatsNewModal: true,
        },
        tipsAndTricks: {
          hasSeenPhotosMessage: false,
        },
      },
      map: {
        initialRegion: null,
      },
      autoSaveEntry: {
        entryText: '',
        timestamp: new Date().getTime(),
      },
      routines: {
        activeRoutines: [],
        routineProgress: {},
        deletedRoutines: [],
        customRoutineItems: [],
      },
      subscriptions: {
        purchases: {},
        lastValidationCheck: null,
        isSubscriptionActive: false,
        latestPurchasedProduct: null,
      },
      activities: [
        ...state.activities,
        ...ADDED_IN_VERSION_3_4,
      ]
    }
  },
  14: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        weather: {
          allowWeather: true,
          unitOfMeasurement: 'metric', // us_custom
        },
        tipsAndTricks: {
          ...state.settings.tipsAndTricks,
          hasSeenMultipleEntriesMessage: false,
        }
      },
    }
  },
  15: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        tipsAndTricks: {
          ...state.settings.tipsAndTricks,
          hasSeenMonthStatisticMessage: false,
        },
        hapticFeedback: {
          allowHapticFeedback: false,
        },
      },
    }
  },
  16: (state) => {
    return {
      ...state,
      settings: {
        ...state.settings,
        layoutCategory: {
          ...state.settings.layoutCategory,
          currentActiveJournalTab: 'entry',
          backgroundOpacity: .72,
          elementBackgroundColor: 100,
          isShowingCurrentWeek: false,
          monthView: 'default',
          menuItems: [...INITIAL_MENU_ITEMS],
          homeComponentWidgets: [...INITIAL_HOME_WIDGETS],
        },
        migration: {
          ...state.migration,
          hasMigratedToAccurateTimestamps: false,
        },
        insightCategory: {
          ratingLineChartCurve: LINE_CHART_CURVE_TYPES[0], // step or average
          timeWindowToDisplay: TIME_WINDOWS[0],
        },
      },
      notifications: {},
      gratitudeJournal: {},
      quotes: {
        todaysQuote: {
          ...strings.Quotes[0],
          dateSet: new Date().getTime(),
        },
        likedQuotes: [],
      },
      emotions: {
        allEmotions: [...ALL_EMOTIONS],
        basicEmotions: [...BASIC_EMOTIONS],
      },
    };
  }
};

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  version: 16,
  timeout: 0,
  migrate: createMigrate(migrations, { debug: true }),
  writeFailHandler: error => console.log('ERROR PERSISTING DATA', error),
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export default () => {
  const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));;
  const persistor = persistStore(store);
  return { store, persistor };
}

Thanks for your detailed description, appreciated.

But maintaining lot of version in future, would be a one more hectic step. Also as per your code example shared by you setting a timeout 0 the application doesn't start it just displays my loader, the state doesn't get rehydrated.

Setting mirgrations object, it doesn't reflect when I am tried to implement & run the application local. It doesn't updates the new additional value.

Any better alternative, which we can do to achieve this scenario, because if I want to add new additional reducer that time also it will result an issue.

vidhyeshpatil avatar May 28 '20 06:05 vidhyeshpatil

Hmm migrations from redux-persist are def the way to go. I would encourage you to stick to this method. All the best!

wmonecke avatar May 28 '20 07:05 wmonecke

Simple answer is: remove/clear storage manually and then refresh page, it will add your updated key value pair

shahmir811 avatar Sep 15 '20 10:09 shahmir811

La respuesta simple es: elimine / borre el almacenamiento manualmente y luego actualice la página, agregará su par de valor clave actualizado

How would I do if my project is in production? it's a bit annoying to ask the user to clean up their local storage

danielm2402 avatar Oct 07 '20 23:10 danielm2402

Simple answer is:

export const migrations = {
  16: () => ({}),
};

DZakh avatar Nov 23 '20 22:11 DZakh

I tried this solution from the official documentation https://github.com/rt2zz/redux-persist/blob/master/docs/migrations.md#alternative and it worked without manually updating the persistConfig version every time.

unigazer avatar Jul 01 '22 15:07 unigazer

@unigazer : Can you describe solution in little more details? Instead or writing migrations like @wmonecke did, we just have to write this code? migrate: (state) => { console.log('Migration Running!') return Promise.resolve(state) } I didn't understand whats given on that link :/

amitpatil321 avatar Jul 13 '22 15:07 amitpatil321

Yes @amitpatil321 I just tested it and it woks for me

Death-thekidd avatar Jan 05 '23 17:01 Death-thekidd

Tôi đã thử giải pháp này từ tài liệu chính thức https://github.com/rt2zz/redux-persist/blob/master/docs/migrations.md#alternative và nó hoạt động mà không cần cập nhật persistConfigphiên bản theo cách thủ công mỗi lần.

I used this method fine, but now it's not working

DatHip avatar Apr 13 '23 11:04 DatHip

Migration can be a way. But I created a function to check the number of keys in reducer with a hard coded value. If they are not equal logout and clear the persisted state in local storage and also do not render the children. You can run this in useEffect hook. This function needs to run at the top level of application (index.js or app.js in react) .


    useEffect(() => {
    const NO_OF_REDUX_STATE_KEYS = 1

      //1) if we add new properties in redux it won't be avalaible in the already loggedin user's redux-persist state, so user needs to login again to maintain the state.
     
if (Object.keys(reduxState).length !== NO_OF_REDUX_STATE_KEYS) {
      setShowChildren(false)
      localStorage.removeItem('persist:Hoichoi')
      router.push('/') // login page
    } else {
        setShowChildren(true)
    }
  }

asamad35 avatar Oct 25 '23 13:10 asamad35