Issue using selector after converting to configureStore Redux Toolkit - reactjs

I've just converted my reducer from createStore to configure store and now my selectors that I think should be working, don't.
configureStore below
import { Dispatch } from 'react';
import { Action, configureStore } from '#reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import logger from 'redux-logger';
import {
testReducer,
} from './reducers';
const rootReducer = () => ({
testReducer
});
export const store = configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(logger),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = (): Dispatch<Action<any>> => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
selector
export const getTest = (state: RootState) => state.testReducer.test;
error
Property 'test' does not exist on type 'ReducerWithInitialState<TestState>'.ts(2339)
The error seems to go away if I add getInitialState() after testReducer. in the selector but that seems incorrect.

That rootReducer you have there is a function that returns an object with reducers - that's wrong. Instead just make it an object.
const rootReducer = {
testReducer
};

Related

How to unwrapResult for all dispatched actions with redux-toolkit

I'm using redux-toolkit and would like to cut down on code duplication by not having to call unwrapResult after every dispatched asynchronous action. I am handling errors for all dispatched actions via displaying a toast message to the user so I'd need to unwrap according to redux-toolkit docs.
Ideally, I'd like to wrap the dispatch function returned from the useDispatch() function from react-redux using a hook something like the following...
// Example of wrapped dispatch function
function SomeComponent() {
const dispatch = useWrappedAppDispatch()
return (
<button
onClick={() =>
dispatch(myAsyncAction())
.catch((_err) =>
toast("Error processing action")
)
}
></button>
)
}
// configureStore.ts
import { configureStore } from "#reduxjs/toolkit"
import { useDispatch } from "react-redux"
import { persistReducer } from "redux-persist"
import storage from "redux-persist/lib/storage"
import { logger } from "../middleware/logger"
import rootReducer from "./reducer"
const persistConfig = {
key: "root",
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false,
}).concat(logger),
})
export { store }
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export type RootState = ReturnType<typeof rootReducer>
export interface GetState {
getState: () => RootState
}
This is possible. To do this we will create a custom hook useAppDispatchUnwrap and return a wrapped redux-toolkit function which will call unwrapResult on all dispatched actions. Just note that by unwrapping all dispatched actions you have to handle any exceptions that are thrown otherwise the app will crash due to uncaught exceptions.
// useAppDispatchUnwrap.ts
import { AsyncThunkAction, unwrapResult } from "#reduxjs/toolkit"
import { AppDispatch, useAppDispatch } from "../store/config/configureStore"
export default function useAppDispatchUnwrap() {
const dispatch = useAppDispatch()
async function dispatchUnwrapped<R extends any>(
action: AsyncThunkAction<R, any, any>
): Promise<R> {
return dispatch(action).then(unwrapResult)
}
return dispatchUnwrapped
}
// configureStore.ts
import { configureStore } from "#reduxjs/toolkit"
import { useDispatch } from "react-redux"
import { persistReducer } from "redux-persist"
import storage from "redux-persist/lib/storage"
import { logger } from "../middleware/logger"
import rootReducer from "./reducer"
const persistConfig = {
key: "root",
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
immutableCheck: false,
}).concat(logger),
})
export { store }
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export type RootState = ReturnType<typeof rootReducer>
export interface GetState {
getState: () => RootState
}
// Example of using wrapped dispatch function
import useAppDispatchUnwrap from "./useAppDispatchUnwrap";
function SomeComponent() {
const dispatch = useAppDispatchUnwrap();
// All dispatched actions are now automatically unwrapped using redux-toolkit "unwrapResult" function.
return (
<button
onClick={() =>
dispatch(myAsyncAction())
.catch((_err) =>
toast("Error processing action")
)
}
></button>
)
}

How to use redux-persist with toolkit and next-redux-wrapper?

I'm having trouble with redux-toolkit, redux-persist, and next-redux-wrapper configuration. I've tried to make persist for redux state but it doesn't run redux actions which should save state to local storage.
My store.ts file.
import {
Action,
combineReducers,
configureStore,
ThunkAction,
getDefaultMiddleware,
} from '#reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createWrapper } from 'next-redux-wrapper';
import taskReducer from './reducers/taskReducer';
import projectReducer from './reducers/projectReducer';
import workplaceReducer from './reducers/workplaceReducer';
import userReducer from './reducers/userReducer';
import trackTaskReducer from './reducers/trackTaskReducer';
import chatRoomReducer from './reducers/chatRoomReducer';
import messageReducer from './reducers/messageReducer';
const rootReducer = combineReducers({
taskReducer,
projectReducer,
workplaceReducer,
userReducer,
trackTaskReducer,
chatRoomReducer,
messageReducer,
});
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const customizedMiddleware = getDefaultMiddleware({
serializableCheck: false,
});
export const setupStore = () => {
return configureStore({
reducer: persistedReducer,
middleware: customizedMiddleware,
});
};
export type AppStore = ReturnType<typeof setupStore>;
export type AppState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action
>;
export const wrapper = createWrapper<AppStore>(setupStore);
My app.tsx file
import React from 'react';
import AdapterDateFns from '#mui/lab/AdapterDateFns';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import { useRouter } from 'next/router';
import type { AppProps } from 'next/app';
import { setupStore, wrapper } from '../store/store';
export default wrapper.withRedux(function MyApp({
Component,
pageProps,
}: AppProps) {
const persistor = persistStore(setupStore());
return (
<PersistGate persistor={persistor} loading={<div>Loading</div>}>
<Component {...pageProps} />
</PersistGate>
);
});
It's saving the initial state to local storage but it isn't saving future changes to the state.
What am I doing wrong?
So i found solution for me:
import {
Action,
combineReducers,
configureStore,
ThunkAction,
getDefaultMiddleware,
} from '#reduxjs/toolkit';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const rootReducer = combineReducers({
// your reducers
});
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const customizedMiddleware = getDefaultMiddleware({
serializableCheck: false,
});
export const setupStore = () => {
return configureStore({
reducer: persistedReducer,
middleware: customizedMiddleware,
});
};
export const persistedStore = () => persistStore(setupStore());
export type AppStore = ReturnType<typeof setupStore>;
export type AppState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action
>;
export const store = setupStore();
Warning: your reducers must have this extra reducer like that:
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.chatRoom,
};
},
Also i don't know if it will work with SSR because i don't need that feature in my case

Persist-reducer function giving type error to my reducer in typescript

I am trying to configure react-persist in my typescript react application. The persistReducer function is giving a type error to my reducer that Argument of type '(state: IState | undefined, action: Action) => IState' is not assignable to parameter of type 'Reducer<unknown, Action>'. Here is my store.ts code.
const persistConfig = {
key: "root",
storage,
stateReconciler: autoMergeLevel2,
whiteList: ["reducer"],
};
const persistedReducer = persistReducer(persistConfig, reducer);//the type error
This the code I am using for my reducers
export const reducer= (state:IState=initialState, action:Action):IState=> {
const {type, payload}=action;
switch(type){
case ActionType.CONNECT_META_MASK:
return {
...state,
address:payload.address,
connection:payload.connection
}
case ActionType.HOUR_PASSED:
return {
...state,
hourPassed:payload
}
default:
return state;
}
}
IState
export interface IState{
address:string,
connection:boolean
hourPassed:number
}
export const initialState:IState={
address: '',
connection: false,
hourPassed:0
}
Action
import {ActionType} from "../types/types"
interface IMetaMaskConnection{
type:typeof ActionType.CONNECT_META_MASK,
payload:{
connection:boolean,
address:string
}
}
interface IHourPassed{
type:typeof ActionType.HOUR_PASSED,
payload:number
}
export type Action = IMetaMaskConnection | IHourPassed
export const connectMetaMaskAction = (data:IMetaMaskConnection['payload']):Action => ({
type: ActionType.CONNECT_META_MASK,
payload:data
});
export const setHourPassed = (data:IHourPassed['payload']):Action => ({
type: ActionType.HOUR_PASSED,
payload:data
});
Instead of Action if I use AnyAction (exported from redux) then it works fine but I lose type declarations for my action payload.
I have looked online but I wasn't able to find any solution.
I had a similar issue while trying to add the persister to my root level reducer. And it took me some time to find a solution so help this directs people to the solution: https://github.com/rt2zz/redux-persist/issues/1140 for this issue. For me to keep type declarations I had to move the persister off of the root level and onto the specific reducer. Here is my implementation of the working persister with working type declarations:
import { configureStore, ThunkAction } from "#reduxjs/toolkit";
import { AnyAction, combineReducers } from "redux";
import thunk from "redux-thunk";
import storage from "redux-persist/lib/storage";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import persistReducer from "redux-persist/es/persistReducer";
import persistStore from "redux-persist/es/persistStore";
import uiReducer, { UiState } from "./reducers/uiReducer";
import apiReducer from "./reducers/apiReducer";
const persistConfig = {
key: "ui",
storage,
stateReconciler: autoMergeLevel2,
};
//Couldn't get state typings if persisting root reducer. Persisted by reducer works.
const reducers = combineReducers({
ui: persistReducer<UiState, any>(persistConfig, uiReducer),
api: apiReducer,
});
export const store = configureStore({
reducer: reducers,
middleware: [thunk],
});
export const persister = persistStore(store);
// Infer the `RootState` from the store itself to set up useAppDispatch and useAppSelector hook: https://react-redux.js.org/using-react-redux/usage-with-typescript
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
AnyAction
>;
UiState is the interface for the initial uiReducer state. This is what kept the type declarations for my setup. I left in some extra stuff to help see the full setup.
In your root reducer, export its type:
import { combineReducers } from "#reduxjs/toolkit";
export const rootReducer = combineReducers({ ... });
export type RootReducer = ReturnType<typeof rootReducer>;
Then import it into your store and set persistReducer as below:
import { rootReducer, RootReducer } from "./reducers";
const persistedReducer = persistReducer<RootReducer>(
persistConfig,
rootReducer
);
Just set the type. i.e:
reducer : persistReducer(persistConfig, rootReducer) as typeof rootReducer
This should give you the autocomplete hints as well.

Error creating redux middleware to add header param to axios request

I'm trying automatically add to all axios requests an access token and I'm using (or trying to) a middleware. But I'm getting the following error: 'store' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
store/index.ts
import { configureStore } from '#reduxjs/toolkit';
import { routerMiddleware } from 'connected-react-router/immutable';
import { createBrowserHistory } from 'history';
import { setIsAxiosAuthorized } from './middlewares';
import createRootReducer from './reducers';
export const history = createBrowserHistory();
export const store = configureStore({
reducer: createRootReducer(history),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
routerMiddleware(history),
setIsAxiosAuthorized
),
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
middlewares.ts
import { authManager } from 'src/features/auth/authManager';
import { authActions } from 'src/features/auth/redux';
import { RootState } from '.';
export const setIsAxiosAuthorized = (store: RootState) => (next) => (
action
) => {
authManager.setAxiosAuthorizationToken();
store.dispatch(authActions.setIsAxiosAuthorized(true));
return next(action);
};
Is my middleware incorrect? How can I make it typed (I've tried different approaches from StackOverflow, but they are causing different errors)?

Property 'then' does not exist on type 'ThunkAction<Promise<string>

I am getting the error
Property 'then' does not exist on type 'ThunkAction<Promise<string>
I am trying to return a promise from a AppThunk in my slice.
below is the code I have setup and I have went through several same questions and followed few suggestions given there, but still could not figure out what is wrong.
store.ts
import { configureStore, Action } from '#reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import { ThunkAction } from 'redux-thunk'
import rootReducer, { RootState } from './rootReducer'
const store = configureStore({
reducer: rootReducer
})
if (process.env.NODE_ENV === 'development' && module.hot) {
module.hot.accept('./rootReducer', () => {
const newRootReducer = require('./rootReducer').default
store.replaceReducer(newRootReducer)
})
}
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>
export default store
mySlice.ts
import { AppThunk } from 'app/store'
// ...
// my reducer logic is here
// ...
export const fetchIssuePromise = (): AppThunk<Promise<string>> => async (dispatch) => {
return Promise.resolve("string")
}
and I am using this in my component.
MyComponent.tsx
import React, { useEffect } from 'react'
import { fetchIssue, fetchIssuePromise } from 'features/mySlice.ts'
import { useDispatch } from 'react-redux'
export const MyComponent = ({
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchIssuePromise()).then(() => {
// My logic here.
})
}, [dispatch])
})
I know I can do this by storing in redux state and using selector and work on that.
But I do not want to store some data especially validation errors from server in redux.
I am not sure if this approach is correct, please let me know how can I handle this or any better ways to achieve this.
Don’t use the default "useDispatch" hook.
As mentionned in this github issue: https://github.com/reduxjs/redux-toolkit/issues/678
The linked doc: https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type
In your store.ts:
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
In your Component.tsx:
const dispatch = useAppDispatch() // hook exported from store

Resources