rt2zz / redux-persist

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

persistReducer returns any with TypeScript #1184

Open sintylapse opened 4 years ago

sintylapse commented 4 years ago

When I use persistReducer it returns any type no matter how I use it, for example

const rootReducer = persistReducer<RootReducer>(config, rootReducer);
// rootReducer is any
YOEL311 commented 3 years ago

That's the only way it worked for me

const persistedReducer = persistReducer<IDemoState>(persistConfig, demoReducer as any); or this way const persistedReducer = persistReducer<any, any>(persistConfig, demoReducer);

rafauke commented 3 years ago

How do you define your rootReducer? Do you pass your State type to combineReducers? Example:

const rootReducer = combineReducers<RootReducerState>({
 apples,
 oranges
});

// somewhere in persist config

const persistedReducer = persistReducer(persistConfig, rootReducer);
R4z1ell commented 2 years ago

I have the same problem

import { configureStore } from '@reduxjs/toolkit';
import { combineReducers } from 'redux';
import { persistReducer } from 'reduxjs-toolkit-persist';
import storage from 'reduxjs-toolkit-persist/lib/storage';
import createSagaMiddleware from 'redux-saga';

import reducers from 'Reducers/index';
import sagas from 'Sagas/index';
import conf from 'Lib/conf';

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

const persistedReducer = persistReducer(persistConfig, combineReducers(reducers));

const sagaMiddleware = createSagaMiddleware();

const devMode = conf.NODE_ENV !== 'production';

export const store = configureStore({
  reducer: persistedReducer,
  middleware: [sagaMiddleware],
  devTools: devMode,
});

sagaMiddleware.run(sagas);

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

If i hover on RootState i get any, if instead inside the configureStore function i don't pass the persistedReducer but simply the reducers like below all is working and i get the types correctly on RootState

export const store = configureStore({
  reducer: reducers,
  middleware: [sagaMiddleware],
  devTools: devMode,
});
fruchtzwerg commented 2 years ago

Just came across this. Seems to be an issue with the return type of persistReducer().

I managed to work around it with a little bit of TS magic:

type InferState<Type> = Type extends Reducer<infer S> ? S : never;

type State<T extends typeof reducers = typeof reducers, P extends keyof T = keyof T> = {
  [K in P]: InferState<T[K]>;
};

const reducers = {
  [FEATURE_A_KEY]: aReducer,
  [FEATURE_B_KEY]: bReducer,
};

const persistedReducer: Reducer<State, AnyAction> = persistReducer<State, AnyAction>(
  persistConfig,
  combineReducers(reducers)
);
Rc85 commented 2 years ago

@fruchtzwerg Getting this error

Argument of type '{ App: Reducer<AppState & PersistPartial, AnyAction>; Snackbar: Reducer<SnackbarState, AnyAction>; Storefront: Reducer<...>; }' is not assignable to parameter of type 'Reducer<State<{ App: Reducer<AppState & PersistPartial, AnyAction>; Snackbar: Reducer<SnackbarState, AnyAction>; Storefront: Reducer<...>; }, "Storefront" | ... 1 more ... | "Snackbar">, AnyAction>'.
  Type '{ App: Reducer<AppState & PersistPartial, AnyAction>; Snackbar: Reducer<SnackbarState, AnyAction>; Storefront: Reducer<...>; }' provides no match for the signature '(state: State<{ App: Reducer<AppState & PersistPartial, AnyAction>; Snackbar: Reducer<SnackbarState, AnyAction>; Storefront: Reducer<...>; }, "Storefront" | ... 1 more ... | "Snackbar"> | undefined, action: AnyAction): State<...>'.

Maybe it's because I have nested persisted reducers. Are you using @reduxjs/toolkit?

fruchtzwerg commented 2 years ago

@Rc85 Probably the nesting, yes. InferState will extract the state from the reducer's generic. If you have nested reducers it will only get the first level. I also had to remove some explicit payload types from my slices. TS can infer them properly though. Yes, I am using @reduxjs/toolkit

sebastien-f commented 1 year ago

Ran into this issue also while using RTK, the simplest way I found to solve it was to let Typescript infer everything from the call to persistReducer by putting the config inline there :

const persistedReducer = persistReducer({
    key: 'cl',
    storage,
}, combinedReducer);

Only weird thing left is that the type of persisted reducer is now Reducer<EmptyObject & MyStateType & PersistPartial>. No idea where that EmptyObject comes from, but I can live with that.

Kymesa commented 1 week ago

RESOLVED

export type RootState = ReturnType<typeof rootReducer>;