rt2zz / redux-persist

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

Data sometimes not persisted in AsyncStorage on Android #1472

Closed fikkatra closed 2 months ago

fikkatra commented 3 months ago

I'm using redux-persist with AsyncStorage in a React Native app. I'm noticing that sometimes, after dispatching an action, the data is updated in the store, but not persisted. It usually happens after a fresh install or hard close of the app. This only happens on Android, on iOS everything works fine.

Scenario:

I'm probably doing something wrong but I can't see what. Any help is appreciated.

Using: @reduxjs/toolkit@2.2.3 react-redux@9.1.1 redux-persist@6.0.0

seenVouchersSlice.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

import { RootState } from '../../store/store';

export interface SeenVouchersState {
  seenVoucherIds: string | undefined;
}

const initialState: SeenVouchersState = {
  seenVoucherIds: undefined,
};

export const seenVouchersSlice = createSlice({
  name: 'seenVouchers',
  initialState,
  reducers: {
    setSeenVoucherIds: (state, { payload }: PayloadAction<string>) => {
      state.seenVoucherIds = payload;
    },
  },
});

export const { setSeenVoucherIds } = seenVouchersSlice.actions;

export const selectSeenVoucherIds = (state: RootState) => state.seenVouchers.seenVoucherIds;

export default seenVouchersSlice.reducer;

store.ts:

import AsyncStorage from '@react-native-async-storage/async-storage';
import { combineReducers, configureStore, createAction } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import createDebugger from 'redux-flipper';
import { persistReducer, persistStore } from 'redux-persist';

import seenVouchersReducer from '../store/seenVouchersSlice';
import { setupListenersReactNative } from './setupListenersReactNative';

const combinedReducer = combineReducers({
  seenVouchers: persistReducer({ key: 'seenVouchers', storage: AsyncStorage }, seenVouchersReducer),
});

export const resetAction = createAction('store/reset');
const rootReducer: typeof combinedReducer = (state, action) => {
  if (action.type == resetAction.type) {
    return combinedReducer(undefined, action);
  }
  return combinedReducer(state, action);
};

export const setupStore = (preloadedState?: Partial<RootState> | undefined) => {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
    middleware: (getDefaultMiddleware) => {
      const middleware = getDefaultMiddleware({
        immutableCheck: {
          ignoredPaths: [],
        },
        serializableCheck: {
          ignoredPaths: [],
          ignoredActions: ['persist/PERSIST', 'persist/PURGE', 'persist/REHYDRATE'],
        },
      });
      if (__DEV__ && !process.env.JEST_WORKER_ID) {
        // redux-flipper typings not yet fully compatible with redux-toolkit v2
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        middleware.push(createDebugger() as (store: any) => (next: any) => (action: unknown) => any);
      }
      return middleware;
    },
  });
};

export const store = setupStore();
setupListeners(store.dispatch, setupListenersReactNative);
// manually persist in Jest to prevent 'open handles error'
export const persistor = persistStore(store, process.env.JEST_WORKER_ID ? ({ manualPersist: true } as any) : undefined);

export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = typeof store.dispatch;

And lastly App.tsx:

import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

import LoadingState from './shared/components/loading/LoadingState';
import { persistor, store } from './store/store';

export default function App() {
  return (
    <Provider store={store}>
      <PersistGate
        persistor={persistor}
        loading={<LoadingState />}>
        {/*  app code here */}
      </PersistGate>
    </Provider>
  );
}
fikkatra commented 2 months ago

After further investigation, it was related to the resetAction action we defined, which was being called erroneously. Closing this, apologies for any effort spent on your part.