rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.97k stars 867 forks source link

redux-persist on typescript #1140

Open ricosandyca opened 4 years ago

ricosandyca commented 4 years ago

Anyone here can give me a simple implementations of a redux-persist on typescript? I always got error like this when i try to create persistor

Argument of type 'Store<{ readonly [$CombinedState]?: undefined; } & { todo: TodoState; } & PersistPartial, TodoActionTypes>' is not assignable to parameter of type 'Store<any, AnyAction>'. Types of property 'dispatch' are incompatible. Type 'Dispatch<TodoActionTypes>' is not assignable to type 'Dispatch<AnyAction>'. Type 'AnyAction' is not assignable to type 'TodoActionTypes'. Type 'AnyAction' is not assignable to type 'DeleteTodoAction'.

This my code

Thanks

wolmeister commented 4 years ago

I also have this error. Changing "strict" to false in tsconfig.json works, but i don't want to disable it.

sunnylqm commented 4 years ago

https://github.com/rt2zz/redux-persist/pull/1085

DGCarramona commented 4 years ago

Hello, Don't know if I should put it here or in new issue. Similar error is found when using persistCombineReducers

markhorsell commented 4 years ago

Had same issue. There was an untyped object I had missed. I set typescript strict to false which found the object for me, was able to fix the object and set typescript strict back to true again. Hope that helps.

chrispaynter commented 4 years ago

My fix may not be so relevant, but this question at least led me to a solution so I'll post it in the off chance someone with the same problem as I comes across this.

I just had to type persistReducer. For example:

persistReducer<RootState>(persistConfig, rootReducer());
rajharshwal92 commented 4 years ago

1085

This PR is solution of this error. Owner of repo kindly merges this PR.

Jonathan0wh commented 4 years ago

I came to this issue from Google about this TypeScript issue:

If I have this rootReducer:

export const rootReducer = combineReducers({
  user: userReducer,
});

and try to use

const persistedReducer = persistReducer(persistConfig, rootReducer);

I will get the following TypeScript error on rootReducer:

Argument of type 'Reducer<CombinedState<{ user: UserState; }>, AnyAction>' is not assignable to parameter of type 'Reducer<unknown, AnyAction>'.
  Types of parameters 'state' and 'state' are incompatible.
    Type 'unknown' is not assignable to type 'CombinedState<{ user: UserState; }> | undefined'.
      Type 'unknown' is not assignable to type 'CombinedState<{ user: UserState; }>'.
        Type 'unknown' is not assignable to type '{ readonly [$CombinedState]?: undefined; }'.ts(2345)

possibly because combineReducers added the readonly [$CombinedState]?: undefined; } type to the rootReducer but wasn't type-checked by persistReducer.

For now, I resolved it by adding the RootState as type parameter:

const persistedReducer = persistReducer<RootState>(persistConfig, rootReducer);

But I hope that the PR #1085 above could possibly resolve this one as well, such that I don't need to manually fill the RootState type. Or maybe a section such as "Troubleshooting" or "Usage with Typescript" could be added to redux-persist README regarding some common issues such as this one and usage together with redux-toolkit.

h3w8529 commented 4 years ago

any update?

Chamnol007 commented 3 years ago

I fixed with:

const persistedReducer = persistReducer<any, any>(persistConfig, RootReducer);

zi-gae commented 3 years ago

1278

This PR is solution of this error.

// redux-persist.d.ts
declare module 'redux-persist/es/persistStore' {
  import { Store, Action, AnyAction } from 'redux';
  import { PersistorOptions, Persistor } from 'redux-persist/es/types';
  // tslint:disable-next-line: strict-export-declare-modifiers
  export default function persistStore<S = any, A extends Action<any> = AnyAction>(
    store: Store<S, A>,
    persistorOptions?: PersistorOptions | null,
    callback?: () => any,
  ): Persistor;
}

declare module 'redux-persist/lib/persistStore' {
  export * from 'redux-persist/es/persistStore';
  export { default } from 'redux-persist/es/persistStore';
}
shuga2704 commented 3 years ago

Any update for this issue?

rayonnunes commented 3 years ago

I had to adjust my createStore into another function named create like this:

import {
  EmptyObject,
  applyMiddleware,
  createStore,
  Middleware,
  Reducer,
} from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistStore, persistReducer } from 'redux-persist'
import { PersistPartial } from 'redux-persist/lib/persistReducer'
import { UserAction, UserState } from './modules/user/types'
import rootReducer from './modules/rootReducer'
import rootSaga from './modules/rootSaga'

export interface StoreState {
  user: UserState // the type of your reducer
}

const create = (
  reducers: Reducer<EmptyObject & StoreState & PersistPartial, UserAction>,
  middlewares: Middleware[],
) => {
  const enhancer = applyMiddleware(...middlewares)
  return createStore(reducers, enhancer)
}

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

const sagaMiddleware = createSagaMiddleware()
const middlewares: Middleware[] = [sagaMiddleware]
const persistedReducer = persistReducer(persistConfig, rootReducer)
// here is the trick instead of createStore I use my modified create function
const store = create(persistedReducer, middlewares)

sagaMiddleware.run(rootSaga)

export const persistor = persistStore(store)
export default store

I suppose you can easily ignore the middleware part if you don't need it:

And Inside of your App component you can use:

<Provider store={store}>
  <PersistGate loading={null} persistor={persistor}>
    <GlobalStyle />
    <Routes />
  </PersistGate>
</Provider>
ezequielaguilera1993 commented 3 years ago

const persistedReducer = persistReducer<RootState, any>(persistConfig, rootReducer)

ruddygasol94 commented 3 years ago

I had to adjust my createStore into another function named create like this:

import {
  EmptyObject,
  applyMiddleware,
  createStore,
  Middleware,
  Reducer,
} from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistStore, persistReducer } from 'redux-persist'
import { PersistPartial } from 'redux-persist/lib/persistReducer'
import { UserAction, UserState } from './modules/user/types'
import rootReducer from './modules/rootReducer'
import rootSaga from './modules/rootSaga'

export interface StoreState {
  user: UserState // the type of your reducer
}

const create = (
  reducers: Reducer<EmptyObject & StoreState & PersistPartial, UserAction>,
  middlewares: Middleware[],
) => {
  const enhancer = applyMiddleware(...middlewares)
  return createStore(reducers, enhancer)
}

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

const sagaMiddleware = createSagaMiddleware()
const middlewares: Middleware[] = [sagaMiddleware]
const persistedReducer = persistReducer(persistConfig, rootReducer)
// here is the trick instead of createStore I use my modified create function
const store = create(persistedReducer, middlewares)

sagaMiddleware.run(rootSaga)

export const persistor = persistStore(store)
export default store

I suppose you can easily ignore the middleware part if you don't need it:

And Inside of your App component you can use:

<Provider store={store}>
  <PersistGate loading={null} persistor={persistor}>
    <GlobalStyle />
    <Routes />
  </PersistGate>
</Provider>

@rayonnunes Do you know since what version it's available EmptyObjet from redux?

I can't find it.

Thanks.

g34r21 commented 3 years ago

1278

declare module 'redux-persist/es/persistStore' {
  import { Store, Action, AnyAction } from 'redux';
  import { PersistorOptions, Persistor } from 'redux-persist/es/types';
  // tslint:disable-next-line: strict-export-declare-modifiers
  export default function persistStore<S = any, A extends Action<any> = AnyAction>(
    store: Store<S, A>,
    persistorOptions?: PersistorOptions | null,
    callback?: () => any,
  ): Persistor;
}

declare module 'redux-persist/lib/persistStore' {
  export * from 'redux-persist/es/persistStore';
  export { default } from 'redux-persist/es/persistStore';
}

This is the best solution, redux-persist should fix this as suggested in this code snippet

sspaceworld commented 2 years ago

You need to change the type of the persistedReducer to <reducer type> & PersistPartial

Example:

If this is the reducer you want to persist.

 const rootReducer = combineReducers<RootState>({
  auth: persistReducer(authPersistConfig, AuthReducer)
});

In RootState, auth property should have this type.

import { PersistPartial } from 'redux-persist/es/persistReducer';

export interface RootState {
  auth: AuthState & PersistPartial;
}
jonra1993 commented 2 years ago

This worked in my case. creating a type RootReducer and add persistReducer< RootReducer >. This example uses RTK query, redux toolkit and redux-persist, and redux-persist-transform-encrypt

rootReducer.ts

import { combineReducers } from '@reduxjs/toolkit';
import { reducer as settingsReducer } from 'src/redux/slices/settings';
import { loginApi } from 'src/services/loginService';
import { nluApi } from 'src/services/nluService';

const appReducer = combineReducers({
  settings: settingsReducer,
  [loginApi.reducerPath]: loginApi.reducer,
  [nluApi.reducerPath]: nluApi.reducer
});

const rootReducer = (state, action) => {
  if (action.type === 'RESET_APP') {
    state = undefined;
  }
  return appReducer(state, action);
};

export const resetAppAction = () => (dispatch) => {
  dispatch({ type: 'RESET_APP' });
};
export type RootReducer = ReturnType<typeof rootReducer>;
export default rootReducer;

index.ts

import { useDispatch as useReduxDispatch, useSelector as useReduxSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { ThunkAction } from 'redux-thunk';
import type { Action } from '@reduxjs/toolkit';
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query/react';
import storage from 'redux-persist/lib/storage';
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import { encryptTransform } from 'redux-persist-transform-encrypt';
import thunk from 'redux-thunk';
import rootReducer, { RootReducer } from './rootReducer';
import { loginApi } from 'src/services/loginService';
import { nluApi } from 'src/services/nluService';

const encryptor = encryptTransform({
  secretKey: process.env.REACT_APP_REDUX_SECRET_KEY,
  onError(error) {
    // Handle the error.
  },
});

const persistConfig = {
  // Root
  key: 'root',
  storage,
  timeout: null,
  blacklist: [
    loginApi.reducerPath,
    nluApi.reducerPath
  ],
  transforms: [encryptor]
};

const persistedReducer = persistReducer<RootReducer>(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV === 'development',
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({
    serializableCheck: {
      ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
    },
  }).concat(
    thunk,
    loginApi.middleware,
    nluApi.middleware
  ),
});

// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch);

export const persistor = persistStore(store);

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk = ThunkAction<void, RootState, null, Action<string>>;

export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;

export const useDispatch = () => useReduxDispatch<AppDispatch>();
TimCrooker commented 2 years ago

if you are using redux/toolkit and are getting this error or losing typing on your selectors then use the following configuration:

store.ts

const rootReducer = combineReducers({
    user: userReducer,
})

const persistedReducer = persistReducer(
    persistConfig,
    rootReducer
) as typeof rootReducer

export const store = configureStore({
    reducer: persistedReducer,
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: false,
        })
})

export const persistor = persistStore(store)

export type AppDispatch = typeof store.dispatch

export type RootState = ReturnType<typeof store.getState>
Asanio06 commented 2 years ago

@TimCrooker Thanks you. It works for me

cloudofgeorge commented 2 years ago

Much better to use the real typings with persistReducer's generics persistReducer<RootReducer, AnyAction>


export const rootReducer = combineReducers({
    user: userReducer,
});

export type RootReducer = ReturnType<typeof rootReducer>;

const persistedReducer = persistReducer<RootReducer, AnyAction>(
  {
    key: 'root',
    storage,
    stateReconciler: hardSet,
  },
  rootReducer,
);

export const store = configureStore({
   reducer: persistedReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      serializableCheck: false,
    }).concat([]),
});