Redux-persist: Issues with flow

Created on 1 Feb 2018  ยท  6Comments  ยท  Source: rt2zz/redux-persist

Hey,
First off, thanks a bunch for this library, and also thanks a bunch for adding types.

I just installed this in an existing codebase and got a whole bunch of errors, starting with:

https://gist.github.com/deecewan/1924ac53d68129148fb56cb53d1d841f

That's a whole load of flow errors from the persistStore function.

Edit: this could maybe be fixed by changing this to:

const persistor = {
    ...createStore(...),
    flush() {},
    purge() {},
};

Happy to open a PR for that, if that'll fix things there. I'm running Flow v0.63 (I notice this lib is v0.59 at the moment - may be the source of issues).

Also, somewhat unrelated, but persistReducer makes my types go weird, because it returns State & { _persist: PersistState }, which doesn't match the type of my State. Is there a reason that this _persist item is stored in the user's state instead of the redux-persist redux store?

Most helpful comment

@rt2zz thanks so much for the detailed response. I figured that you had probably looked at this, and was mostly just curious as to why this decision was made.

I'm interested to see what the eventual solution is, but for now I'm happy to go back to manually adding the key.

All 6 comments

@rt2zz I think #697 will fix the majority of the flow errors, but still curious around why _persist is stored in the user's store rather than the store created with persistStore?

Doing this messes with my State type, because instead of being as expected, it's got an extra property on it, which I'm not too keen on adding manually because it looks like it's private.

@deecewan we might change this in v6 - but its definitely not a cut and dry issue from my perspective. Here is some context:

pros:

  1. keeping _persist in the reducer state makes everything atomic, we can never have state be out of sync with its persistence state (namely version).
  2. it preserves the redux concept of a "single state atom" -> everything stateful is inspectable in one place.
  3. it keeps the code simple, since a store may have multiple persisted reducers, storing it along side state makes it dead simple to find the persist state for each persistoid.

cons:

  1. changes state typings
  2. we have to workaround combineReducers unexpected state warning
  3. its unexpected for users
  4. it prevents mounting multiple persists onto the same reducer (i.e. at the same mount point)

Con 4 is a big issue for me and I would definitely like to explore options for how we can resolve this. Perhaps storing _persist state in the persistor is the way to go, but then we have to worry about syncing it and persisting it.

While I am open to suggestion I think this is unlikely to change in the near future.

In the meantime I would recommend you add the _persist state to your State type. We export the PersistState shape so you can do something like

import { type PersistState } from 'redux-persist/src/types'

type State = {
  //... your state shape
  _persist: PersistState
}

@rt2zz thanks so much for the detailed response. I figured that you had probably looked at this, and was mostly just curious as to why this decision was made.

I'm interested to see what the eventual solution is, but for now I'm happy to go back to manually adding the key.

For anyone else who stumbles in here, I could not get my setup to play nicely with

import { type PersistState } from 'redux-persist/src/types'

type State = {
  //... your state shape
  _persist: PersistState
}

I got errors from some middleware, etc, and I didn't have time to diagnose.
I ended up just casting the persistReducer call and essentially ignoring the _persist key. I don't want that key used in the application itself, so this works out well.

My code looks like:

import type { State, Actions } from '../types/store';

const persistedReducer: (State, Actions) => State = persistReducer(config, reducer);

Now, flow is happy and I am happy.

I'm having similar problems and can not get it working. This is my store config:

  const baseReducer: Reducer<StateType, StoreActionType> = combineReducers({
    uiDirection: uiDirectionReducer,
    language: languageReducer,
    darkMode: toggleDarkModeReducer,
    network: reactNativeOfflineReducer,
    data: combineReducers({
      cities: citiesReducer,
      categories: categoriesReducer
    })
  })

  const rootReducer: Reducer<PersistedStateType, StoreActionType> = persistReducer(persistConfig, baseReducer)

  const middleware = applyMiddleware(createNetworkMiddleware(), sagaMiddleware, createLogger())

  const store: Store<PersistedStateType, StoreActionType> = createStore(rootReducer, middleware)

The type of my store is:

export type StateType = {
  uiDirection: string,
  language: string,
  darkMode: boolean,
  network: { isConnected: boolean, actionQueue: Array<StoreActionType> },
  data: {
    cities: any,
    categories: any
  }
}

export type PersistedStateType = {
  uiDirection: string,
  language: string,
  darkMode: boolean,
  network: { isConnected: boolean, actionQueue: Array<StoreActionType> },
  data: {
    cities: any,
    categories: any
  },
  _persist: PersistState
}

But I get the following error:

Error โ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆ src/modules/app/createReduxStore.js:91:69

Cannot assign persistReducer(...) to rootReducer because:
 โ€ข object type [1] is incompatible with undefined [2] in the first argument.
 โ€ข property darkMode is missing in PersistPartial [3] but exists in PersistedStateType [4] in the return value.
 โ€ข property data is missing in PersistPartial [3] but exists in PersistedStateType [4] in the return value.
 โ€ข property language is missing in PersistPartial [3] but exists in PersistedStateType [4] in the return value.
 โ€ข property network is missing in PersistPartial [3] but exists in PersistedStateType [4] in the return value.
 โ€ข property uiDirection is missing in PersistPartial [3] but exists in PersistedStateType [4] in the return value.

     src/modules/app/createReduxStore.js
     88โ”‚     })
     89โ”‚   })
     90โ”‚
 [4] 91โ”‚   const rootReducer: Reducer<PersistedStateType, StoreActionType> = persistReducer(persistConfig, baseReducer)
     92โ”‚
     93โ”‚   const middleware = applyMiddleware(createNetworkMiddleware(), sagaMiddleware, createLogger())
     94โ”‚

     flow-typed/npm/redux_v4.x.x.js
 [2] 30โ”‚   declare export type Reducer<S, A> = (state: S | void, action: A) => S;

     node_modules/redux-persist/lib/persistReducer.js.flow
 [1] 30โ”‚ export default function persistReducer<State: Object, Action: Object>(
     31โ”‚   config: PersistConfig,
     32โ”‚   baseReducer: (State, Action) => State
 [3] 33โ”‚ ): (State, Action) => State & PersistPartial {

The main problem is this:

Error โ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆโ”ˆ src/modules/app/createReduxStore.js:86:64

Cannot call createStore because StateType [1] is incompatible with undefined [2] in the first argument.

     src/modules/app/createReduxStore.js
 [1]  82โ”‚   const rootReducer: (state: StateType, action: StoreActionType) => StateType = baseReducer
      83โ”‚
      84โ”‚   const middleware = applyMiddleware(createNetworkMiddleware(), sagaMiddleware, createLogger())
      85โ”‚
      86โ”‚   const store: Store<StateType, StoreActionType> = createStore(rootReducer, {
      87โ”‚     uiDirection: uiDirectionReducer,
      88โ”‚     language: '',
      89โ”‚     darkMode: false,
      90โ”‚     network: {isConnected: false, actionQueue: []},
      91โ”‚     data: {
      92โ”‚       cities: {},
      93โ”‚       categories: {}
      94โ”‚     },
      95โ”‚     _persist: {
      96โ”‚       version: 0,
      97โ”‚       rehydrated: false
      98โ”‚     }
      99โ”‚   }, middleware)
     100โ”‚
     101โ”‚   sagaMiddleware.run(rootSaga)
     102โ”‚

     flow-typed/npm/redux_v4.x.x.js
 [2]  30โ”‚   declare export type Reducer<S, A> = (state: S | void, action: A) => S;

The redux Reducer type uses S | void as type for the state. But redux-persist uses an Object in the reducer definition.
I got it working by handling the void case separately:

  const persistConfig = {
    key: 'network',
    storage: AsyncStorage
  }

  const initialState = {
    uiDirection: uiDirectionReducer,
    language: '',
    darkMode: false,
    network: {isConnected: false, actionQueue: []},
    data: {
      cities: {},
      categories: {}
    },
    _persist: {
      version: 0,
      rehydrated: false
    }
  }

  const baseReducer: (state: StateType | void, action: StoreActionType) => StateType = (state, action) => {
    if (!state) {
      return initialState
    }

    return persistCombineReducers(persistConfig, {
      uiDirection: uiDirectionReducer,
      language: languageReducer,
      darkMode: toggleDarkModeReducer,
      network: reactNativeOfflineReducer,
      data: combineReducers({
        cities: citiesReducer,
        categories: categoriesReducer
      })
    })(state, action)
  }

  const middleware: StoreEnhancer<StateType, StoreActionType> = applyMiddleware(createNetworkMiddleware(), sagaMiddleware, createLogger())

  const store: Store<StateType, StoreActionType> = createStore(baseReducer, initialState, middleware)
Was this page helpful?
0 / 5 - 0 ratings