19

I'm moving my React / Redux application to Redux toolkit, and am following instructions given here.

My custom selector and dispatch, as per the documentation.

import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "@/index";

export const useMyDispatch = () => useDispatch<AppDispatch>();
export const useMySelector: TypedUseSelectorHook<RootState> = useSelector;

Type of useMyDispatch is useMyDispatch(): Dispatch<AnyAction>

Type GETALLPAGESACTION is

export type GETALLPAGESACTION = {
  pagesLoading?: boolean;
  pages?: PageType[] | null;
  error?: Error | null;
}

This is my very basic action:

export const getAllPages = createAsyncThunk<GETALLPAGESACTION, 
 string,
 { dispatch: AppDispatch; state: RootState; }>("pages/getAllPages", 
   async () => {
   const pagesRes = await axios.get("___");
   if (pagesRes) {
     const finalPages: PageType[] = [];
     pagesRes.data.forEach((page: PageType) => {
     finalPages.push(page);
   });
   return { pages: finalPages, pagesLoading: false };
 }
 return { pages: [], pagesLoading: false };
});

My store is a simple:

const appStore = configureStore({
  reducer: {
    pages: PagesReducer, // pagesSlice.reducer is default export
  },
});
export type RootState = ReturnType<typeof appStore.getState>;
export type AppDispatch = typeof appStore.dispatch;

But when I try to use getAllPages in my component I get the above error.

useEffect(() => {
  dispatch(getAllPages());  // Error here 
 }, []);

Argument of type 'AsyncThunkAction<GETALLPAGESACTION, string, { dispatch: Dispatch<AnyAction>; state: { pages: { pages: null; pagesLoading: boolean; }; }; }>' is not assignable to parameter of type 'AnyAction'.

I know this question has been asked several times, and I have tried adding those solutions, including trying to set an explicit middleware type, which controls the dispatch type, but none of that has worked. From what I can understand (I'm relatively new to Typescript), the dispatch type is not being inferred correctly here?

This is my slice

export const pagesSlice = createSlice({
  name: "pages",
  initialState,
  reducers: {},
  extraReducers: {
    [getAllPages.pending]: (state, action: PayloadAction<GETALLPAGESACTION>) =>   {
      state.pagesLoading = true;
    },
    [getAllPages.fulfilled]: (state, action: PayloadAction<GETALLPAGESACTION>) => {
      state.pages = action.payload.pages;
      state.pagesLoading = false;
    },
   [getAllPages.rejected]: (state, action: PayloadAction<GETALLPAGESACTION>) => {
      state.pagesLoading = false;
   },
 },
});
export default pagesSlice.reducer;

I could really use some help here, especially with an explanation of what is wrong here and what I should dig into further to understand this better. Thanks!

kvnam
  • 1,285
  • 2
  • 19
  • 34

3 Answers3

33

In my case I had to create useAppDispatch function and use it instead useDispatch in component from react-redux.

export type AppDispatch = typeof store.dispatch

export const useAppDispatch = () => useDispatch<AppDispatch>()
thobho
  • 331
  • 3
  • 4
  • 3
    This was also my issue. Documentation on this issue can be read [here](https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type) – shennan Aug 10 '22 at 15:28
  • You might not need to wrap it into another function, but just type the useDispatch. In my case I did ` const dispatch = useDispatch()` inside my component – A Mehmeto Jan 11 '23 at 13:28
  • also my issue, for types to work with typescript need to do this, accidentally had useDispatch(); called instead of the useAppDispatch as per RTK docs. – RJA Feb 02 '23 at 20:11
27

Most of the time this is just a bug, caused by Redux 4.0.5 and Redux 4.1.0/4.1.1 being installed both somewhere in your node_modules.

Most of the time, this can be resolved by reinstalling react-redux, @types/react-redux and @reduxjs/toolkit.

If you are using yarn, you can also run yarn why redux and it will list you all installed versions and why they are installed.


Update June 2022:

The same also happens if you install react-redux in v.8 and still have @types/react-redux in v.7 installed. react-redux 8 ships with it's own type definitions - in that case please uninstall @types/react-redux.

phry
  • 35,762
  • 5
  • 67
  • 81
  • 2
    Thank you!! You were spot on, I did have 4.0.5 and 4.1.1 installed, re-installing the mentioned packages resolved this issue. – kvnam Aug 17 '21 at 07:45
  • is there an issue opened in github about this? @phry – Chanwoo Park Sep 14 '21 at 11:22
  • @ChanwooPark what would we do about it? Release another version that adds even more to the confusion? In the end, people should never have multiple versions of the same library installed side by side as that can always cause TypeScript problems, but that's a package manager problem. We can't do anything about that. – phry Sep 14 '21 at 14:10
  • 1
    @phry is there another possible reason why this occurs even tho only one 'redux' package is installed ? using redux version is 4.1.2 – Chanwoo Park Nov 30 '21 at 05:30
  • 1
    @ChanwooPark if you are not using the typed dispatch hooks recommended in the RTK TypeScript quickstart docs or passing an array into `middlewares` of `configureStore`, accidentally removing the thunk middleware. – phry Nov 30 '21 at 07:09
  • 5
    @ ChanwooPark don't use `middlewares` array, instead use `(getDefaultMiddleware) => getDefaultMiddleware().concat(thunkMiddleware)` – parse Mar 16 '22 at 21:59
  • Same error. But for me it was `react-redux` version. changed 8.0.1 to 7.2.6. "react-redux": "7.2.6", "@types/react-redux": "^7.1.24", "@reduxjs/toolkit": "^1.8.1", – tharinduPro Jun 07 '22 at 09:07
  • @tharinduPro if you have `react-redux`>=8, you don't need `@types/react-redux` at all any more. Please just uninstall that and everything should be fine. – phry Jun 07 '22 at 09:07
2

My issue was how I defined my store's middleware. In order for TypeScript to determine the store's actions, you must use .concat and .prepend rather than defining a new array with a spread operator. No idea why.

Good

const store = configureStore({
  // ...
  middleware: getDefaultMiddleware => 
    getDefaultMiddleware()
      .prepend(/* ... */)
      .concat(/* ... */),
    // ...
  ],
  // ...
})

Bad

const store = configureStore({
  // ...
  middleware: getDefaultMiddleware => [
    // ...
    ...getDefaultMiddleware(),
    // ...
  ],
  // ...
})
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102