rt2zz / redux-persist

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

[Android] redux-persist with AsyncStorage is storing data to Sqlite as columns #1265

Closed sungsong88 closed 3 years ago

sungsong88 commented 3 years ago

I have an app that can become quite large in terms of the size of data. When a user has more than 2000 documents, and he tries to sign in I noticed that redux-persist fails silently and nothing is persisted.

Array with 2000 notes works, but 2001 won't.

I was wondering why this is happening on Android. And bumped into the list of SQLite default config values and saw that SQLITE_MAX_COLUMN is 2000 as default.

All the other default values are huge except SQLITE_MAX_COLUMN and it sorta makes sense. No one wants their tables with 2000 columns, for sure.

But I'm afraid that I think react-persist is giving a brand new column to SQLite for every new element in the array.

The expected behavior is to persist the data as a new row for every new element in the array instead of a new column.(Android SQLite)

Here is how my data storage look like:

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
};

const persistedReducer = persistReducer(persistConfig, combineReducers({
  themeReducer, 
  languageReducer, 
  navigationStateReducer, 
  userReducer, 
  notificationsReducer, 
  mediaReducer, 
  notesReducer, 
  dataManagementReducer, 
  attachmentsReducer, 
  searchReducer,
  appGuideReducer,
  appRatingReducer,
  authsReducer,
  deviceReducer
}));

And for notes reducer, it looks like this, and that array of notes is where it fails when it's greater than 2000:

const initialState = {
  notes: [],
  deleted: []
};

const notesReducer = (state = initialState, action) => {
  if((action.type === "USER_SIGN_IN" || action.type === "USER_SIGN_UP") && action.payload?.notes) {
    return {
      ...state,
      notes: action.payload.notes,
    };
  }
  ...
}

I had also tried to make the entire state as an Object of notes but didn't work either.

const initialState = {};

const notesReducer = (state = initialState, action) => {
  if((action.type === "USER_SIGN_IN" || action.type === "USER_SIGN_UP") && action.payload?.notes) {
    const newNotes = {};
    action.payload.notes.map(note => newNotes[note.id] = note);

    return newNotes;
  }
  ...
}

I don't know how it's done on iOS, but persisting more than 2000 notes working just fine there. Just Android SQLite

sungsong88 commented 3 years ago

Sorry for being a troll.

For nested reducers, I must use the separated persist function.

This is how I solved it:

const persistedReducer = combineReducers({
  themeReducer: persistReducer({
    key: 'theme',
    storage: AsyncStorage,
  }, themeReducer), 
  languageReducer: persistReducer({
    key: 'language',
    storage: AsyncStorage,
  }, languageReducer), 
  navigationStateReducer: persistReducer({
    key: 'navigation_state',
    storage: AsyncStorage,
  }, navigationStateReducer), 
  userReducer: persistReducer({
    key: 'user',
    storage: AsyncStorage,
  }, userReducer), 
  notificationsReducer: persistReducer({
    key: 'notifications',
    storage: AsyncStorage,
  }, notificationsReducer), 
  mediaReducer: persistReducer({
    key: 'media',
    storage: AsyncStorage,
  }, mediaReducer), 
  notesReducer: persistReducer({
    key: 'notes',
    storage: AsyncStorage,
  }, notesReducer), 
  dataManagementReducer: persistReducer({
    key: 'data_management',
    storage: AsyncStorage,
  }, dataManagementReducer), 
  attachmentsReducer: persistReducer({
    key: 'attachments',
    storage: AsyncStorage,
  }, attachmentsReducer), 
  searchReducer: persistReducer({
    key: 'search',
    storage: AsyncStorage,
  }, searchReducer),
  appGuideReducer: persistReducer({
    key: 'app_guide',
    storage: AsyncStorage,
  }, appGuideReducer),
  appRatingReducer: persistReducer({
    key: 'app_rating',
    storage: AsyncStorage,
  }, appRatingReducer),
  authsReducer: persistReducer({
    key: 'auths',
    storage: AsyncStorage,
  }, authsReducer),
  deviceReducer: persistReducer({
    key: 'device',
    storage: AsyncStorage,
  }, deviceReducer)
});