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)?
Related
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>
)
}
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
};
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.
I'm trying to dispatch an action (don't know if it's the right name, because I'm really new to redux and redux-toolkit) and I'm getting this error:
Argument of type 'AsyncThunkAction<Device, number, { dispatch: Dispatch<AnyAction>; }>' is not assignable to parameter of type 'AnyAction'.
Property 'type' is missing in type 'AsyncThunkAction<Device, number, { dispatch: Dispatch<AnyAction>; }>' but required in type 'AnyAction'. TS2345
18 | if (hasGetError) push('/');
19 | else {
> 20 | dispatch(getDeviceById(deviceId));
| ^
21 | }
22 | }, [push, hasGetError, dispatch, deviceId]);
23 |
Here is my code:
store\index.ts
import { configureStore } from '#reduxjs/toolkit';
import { routerMiddleware } from 'connected-react-router/immutable';
import { createBrowserHistory } from 'history';
import thunkMiddleware from 'redux-thunk';
import createRootReducer from './reducers';
export const history = createBrowserHistory();
export const store = configureStore({
reducer: createRootReducer(history),
middleware: [routerMiddleware(history), thunkMiddleware],
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
features\device\redux\deviceOperations.ts
import { createAsyncThunk } from '#reduxjs/toolkit';
import { AppDispatch } from 'src/store';
import { loaderActions } from '../../../features/loader/redux';
import deviceManager from '../deviceManager';
export const getDeviceById = createAsyncThunk<
Device,
number,
{
dispatch: AppDispatch;
}
>('device/GET_DEVICE_BY_ID', async (deviceId: number, thunkApi) => {
thunkApi.dispatch(loaderActions.start('getDeviceById'));
try {
const device = await deviceManager.getDeviceById(deviceId);
return { ...device, deviceId };
} finally {
thunkApi.dispatch(loaderActions.stop('getDeviceById'));
}
});
interface Device {
deviceId: number;
name: string;
}
The part that is causing this error seems to be a custom hook (I'm following another project, but I don't really understand why I need this custom hook and how to use it in my component, I'm really following the example).
features\device\hooks\useGetDeviceById.ts
import { useEffect } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from 'src/hooks';
import { fetchStatus } from 'src/shared/constants';
import { getDeviceById } from 'src/features/device/redux/deviceOperations';
import {
getDevice,
getDeviceFetchError,
} from 'src/features/device/redux/deviceSelectors';
const useGetDeviceById = () => {
const dispatch = useAppDispatch();
const { push } = useHistory();
const { deviceId } = useParams<{ deviceId?: string }>();
const device = useAppSelector(getDevice);
const hasGetError = useAppSelector(getDeviceFetchError);
const isDeviceLoading =
device.fetchStatus.getProposalById !== fetchStatus.fulfilled;
useEffect(() => {
if (hasGetError) push('/');
else {
dispatch(getDeviceById(deviceId)); // ERROR
}
}, [push, hasGetError, dispatch, deviceId]);
return { device: { ...device, deviceId }, isDeviceLoading };
};
export default useGetDeviceById;
How can I fix it? And if I'm not asking much, why would I really need this custom hook?
The issue you are running into is because you are overriding the middleware that redux-toolkit usually applies which sets up the type definitions properly.
You can fix the issue using getDefaultMiddleware which will allow you to concat on the connected router middleware without ruining the setup.
import { configureStore } from "#reduxjs/toolkit";
import { routerMiddleware } from "connected-react-router/immutable";
import { createBrowserHistory } from "history";
import createRootReducer from "./reducers";
export const history = createBrowserHistory();
export const store = configureStore({
reducer: createRootReducer(history),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(routerMiddleware(history))
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Here's an approximation of the code (enough at least for those type errors to show/be fixed):
https://codesandbox.io/s/strange-taussig-oweoj?file=/src/useGetDeviceById.ts
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