Reputation: 1131
store.ts
export const store = configureStore({
reducer: {
auth: authReducer
},
middleware: [],
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
hooks.ts
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
authSlice.ts (function that causes the problem)
export const fetchUser = createAsyncThunk(
'users/fetchByTok',
async () => {
const res = await getUser();
return res.data;
}
)
Auth.ts
const Auth = ({ component, isLogged }: {component: any, isLogged: boolean}) => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchUser()) // <----------- ERROR
}, []);
return isLogged ? component : <Navigate to='/sign-in' replace={true} />;
}
export default Auth;
I have a createAsyncThunk function that fetches the user, but I cannot actually put it in the dispatch()...
First time using this, so a nice explanation would be nice :).
Upvotes: 56
Views: 71074
Reputation: 19
// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import recommendReducer from './modules/recommend.ts'
const store = configureStore({
reducer: {
recommend: recommendReducer
}
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store
// src/hooks/app.ts
import type { AppDispatch, RootState } from '@/store'
import type { TypedUseSelectorHook } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppDispatch: () => AppDispatch = useDispatch
I repackaged the useDispatch hooks function based on the store type of my project according to the official documentation of redux. When using dispatch, just use the encapsulated one. This way, there is ts type support.
Upvotes: 1
Reputation: 195
in my case, After adding <any>
issues was resolved.
const dispatch = useDispatch<any>();
useEffect(() => {
dispatch(fetchProducts());
}, [])
Upvotes: 1
Reputation: 36
I figured out for my case, what if you use configureStore
from '@reduxjs/toolkit'
and want to use middlewares, then you can't write something like this:
export const store = configureStore({
reducer: {
r1: r1Reducer,
},
devTools: true,
middleware: [
reduxLogger
],
});
instead of this you should use this:
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(reduxLogger),
or use answer by @andrewnosov
Upvotes: 2
Reputation: 11
Just follow the Redux Toolkit docs for using useDispatch
and useSelector
in TypeScript.
Firstly, export RootState
and AppDispatch
from your store.ts file:
import { configureStore } from '@reduxjs/toolkit'
// ...
export const store = configureStore({
reducer: {
posts: postsReducer,
comments: commentsReducer,
users: usersReducer,
},
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
Then create a hooks.ts file to create typed version of useDispatch
and useSelector
hooks, this basically saves you from extra typing:
import { useDispatch, useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
Now you can simply import useAppDispatch
and useAppSelector
which are the typed version of useDispatch
and useSelector
from your hooks.ts file:
import {useAppDispatch, useAppSelector} from "app/hooks";
// ...
export const App = () => {
const dispatch = useAppDispatch();
const state = useAppSelector((state) => state);
}
Upvotes: 1
Reputation: 624
Here's what the Usage with TypeScript doc suggest that eventually works for me:
In store.ts
export type AppDispatch = typeof store.dispatch
In hooks.ts
export const useAppDispatch: () => AppDispatch = useDispatch
In yourComponent.tsx
const dispatch = useAppDispatch()
dispatch(yourThunkAction)
Upvotes: 2
Reputation: 21
For me it was a problem with typescript, adding createAsyncThunk()
types according to docs solved the issue. more info on: https://redux-toolkit.js.org/api/createAsyncThunk especially the last parts.
Upvotes: 0
Reputation: 1577
If you do not have any middlewares, don't add it as empty array, just remove it.
If you have some middlewares you have to merge it with default middleware using concat
method like
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(yourMiddleware)
Never use spread operator to merge the middlewares. Spread operator will not retain the types but concat
method does.
Upvotes: 1
Reputation: 2648
This worked for me
import {ThunkDispatch} from "@reduxjs/toolkit";
const dispatch = useDispatch<ThunkDispatch<any, any, any>>();
Upvotes: 19
Reputation: 403
You've replaced default middlewares with empty array in your configuration of store. So, your app don't have redux-thunk.
You can do something like:
const middleware = [
...getDefaultMiddleware(),
/* custom middlewares */
];
and then add this array to configuration object.
Upvotes: 0
Reputation: 471
import * as reduxThunk from "redux-thunk/extend-redux";
Try adding this import statement in redux store configuration file or root file - app.js/index.js or in case of next js - _app.js in pages directory.
eg : store.js
import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";
import * as reduxThunk from "redux-thunk/extend-redux";
export const store = configureStore({
reducer: rootReducer,
});
export default store;
Upvotes: 6
Reputation: 1089
Simplest solution for me was to replace:
const dispatch = useDispatch();
with:
const dispatch = useDispatch<any>();
Upvotes: 13
Reputation: 819
The rest of the answers suggest updating the type of store.dispatch
by inference, which I too prefer.
Here, I want to suggest an alternative using explicit type definitions if, for some reason, you fail to solve it through inference (which can happen in larger projects, etc.)
So the idea here is to explicitly define the type of your redux store with an updated dispatch type which supports thunk actions.
// your redux store config file.
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import reducers from "./path/to/reducers";
// ... your code
// 1. Get the root state's type from reducers
export type RootState = ReturnType<typeof reducers>;
// 2. Create a type for thunk dispatch
export type AppThunkDispatch = ThunkDispatch<RootState, any, AnyAction>;
// 3. Create a type for store using RootState and Thunk enabled dispatch
export type AppStore = Omit<Store<RootState, AnyAction>, "dispatch"> & {
dispatch: AppThunkDispatch;
};
//4. create the store with your custom AppStore
export const store: AppStore = configureStore();
// you can also create some redux hooks using the above explicit types
export const useAppDispatch = () => useDispatch<AppThunkDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Using the above method you can also use store.dispatch
to dispatch the async thunk actions (might be helpful when you are writing tests)
or use the useAppDispatch
hook
or use the old school connect
consumer function from react-redux
.
It will give you correct types in all the cases.
I personally prefer inference-type declarations most of the time, but sometimes we don't have a choice because of external or internal reasons.
I hope this answer is helpful. Thank you :)
Upvotes: 23
Reputation: 363
For me the solution was to stick more closely to the RTK documentation example.
So using concat
...
const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(FooApi.middleware, apiErrorMiddleware),
...rest_of_the_config,
});
...instead of spreading the array...
const store = configureStore({
middleware: (getDefaultMiddleware) =>
[...getDefaultMiddleware(), FooApi.middleware, apiErrorMiddleware],
...rest_of_the_config,
});
Upvotes: 15
Reputation: 1963
I faced the same issue, for me it was just solved by adding AppDispatch
to the type of useDispatch
hook;
const dispatch = useDispatch<AppDispatch>();
useEffect(() => {
dispatch(getUsers());
}, []);
getUsers() was my createAsyncThunk
function
Upvotes: 151
Reputation: 1237
EDIT!
Since as mentioned in the comment, redux toolkit actually adds Thunk by default, the answer from Phry is more accurate. I can't delete the accepted answer, so this edit would have to suffice.
The answer that I provided will remove other middlewares that are automatically added!
The problem is that you're actually missing the thunk middleware inside store configuration. Just add an import for thunkMiddleware
and add it in the middleware
array in your configuration. Since the middleware is not added, the dispatch won't accept the Thunk Action, because it is not supported by redux out of a box.
import thunkMiddleware from 'redux-thunk';
export const store = configureStore({
reducer: {
auth: authReducer
},
middleware: [thunkMiddleware],
});
Upvotes: 8
Reputation: 44078
There is a common TS issue that surfaces like this if you have redux
in the versions 4.0.5 and 4.1.x both somewhere in your node_modules
.
For many people, uninstalling and re-installing react-redux
or @types/react-redux
seems to solve the problem.
Otherwise your bundler might help you find the source of that problem (npm ls redux
or yarn why redux
if you are using one of those two).
Upvotes: 1