Hi. Thanks for a really cool toolkit which shortened my redux code by half.
However, I have some issues with createAsyncThunk().
I have the next action:
export const searchNames = createAsyncThunk<string[], string>(
'@apt/SEARCH_NAMES',
async (packageName: string) => {
console.log('searchNames called on ', packageName)
return ['asd', 'sdsa']
}
)
which I'm trying to use in a component that connects to Redux store with mapDispatchToProps:
const mapDispatchToProps = (dispatch: Dispatch<RootAction>) =>
bindActionCreators(
{
// ...
searchNames: AptActions.searchNames
},
dispatch
)
The RootAction type is declared globally and has the type of:
type RootAction = ReturnType<typeof store.dispatch>
In my component I'm trying to do:
const fetchCompletion = async (name: string) => {
try {
const response = await searchNames(name)
if (searchNames.fulfilled.match(response)) {
const names = unwrapResult(response)
setOptions(
names.sort((a: string, b: string) => leven(a, name) - leven(b, name)).slice(0, 5)
)
}
} catch (e) {
setOptions([])
}
setLoading(false)
}
But I'm facing an error on match function:
TS2345: Argument of type 'AsyncThunkAction<string[], string, {}>' is not assignable to parameter of type 'Action<unknown>'. Property 'type' is missing in type 'AsyncThunkAction<string[], string, {}>' but required in type 'Action<unknown>'. index.d.ts(21, 3): 'type' is declared here. (link to redux/index.d.ts)
Did I miss something? Using useDispatch hook leads to the same result.
I've tried to call
AptActions.searchNames.fulfilled.match(response)
But it doesn't change anything.
As my IDE says, response has the next type:
const response: AsyncThunkAction<string[], string, {}>
When searchNames is next:
searchNames: (arg: string) => AsyncThunkAction<string[], string, {}>
Also IDE says await is unnecessary.
TS80007: 'await' has no effect on the type of this expression.
You need to correctly type the Dispatch type - currently you are probably using the Dispatch type from react-redux, which does not contain the ThunkDispatch type, that is actually part of the store.dispatch provided be thunk middleware included by configureStore.
So don't use dispatch: Dispatch<RootAction>, but define type AppDispatch = typeof store.dispatch and use that instead.
(see https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type )
@h0tw4t3r In addition, you'll most likely want to move ...getDefaultMiddleware() to the end of your middleware array in store.ts like:
const middleware = [
routerMiddleware(history),
logger as Middleware,
...getDefaultMiddleware()
] as const
// create store
const store = configureStore({
reducer: rootReducer,
preloadedState: initialState,
devTools: true,
middleware
})
See #559 for more info.
So don't use
dispatch: Dispatch<RootAction>, but definetype AppDispatch = typeof store.dispatchand use that instead.
I don't understand how that can be different from my RootAction since it does almost the same thing, but I've tried though.
The outcome is the same, my global typing looks like this:
My types.d.ts:
import store from './index'
declare global {
type Store = { [K in keyof typeof store]: ReturnType<typeof store[K]> }
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export type RootAction = ReturnType<typeof store.dispatch>
}
mapDispatchToProps now looks like this:
const mapDispatchToProps = (dispatch: AppDispatch) =>
bindActionCreators(
{
clearAlert: AlertActions.clear,
setAlert: AlertActions.set,
searchNames: AptActions.searchNames,
push
},
dispatch
)
And connected component still has the same error:
TS2345: Argument of type 'AsyncThunkAction<string[], string, {}>' is not assignable to parameter of type 'Action<unknown>'. Property 'type' is missing in type 'AsyncThunkAction<string[], string, {}>' but required in type 'Action<unknown>'.
I really would like to use connect instead of useAppDispatch hook.
@h0tw4t3r In addition, you'll most likely want to move
...getDefaultMiddleware()to the end of your middleware array instore.tslike:
Thanks, done!
So don't use
dispatch: Dispatch<RootAction>, but definetype AppDispatch = typeof store.dispatchand use that instead.I don't understand how that can be different from my
RootActionsince it does almost the same thing, but I've tried though.
It is a very different thing.
Dispatch<RootAction> is <T extends RootAction>(action: T) => T.
So an object goes in (usually extening Action, which means that it needs to have a type property), and the same object comes out as a result.
But the type of store.dispatch contains (depending on your middleware configuration) also the signature of ThunkDispatch
ThunkDispatch is that AND <T>(thunkAction: (dispatch, getStore) => T) => T
So the normal dispatch is missing the overload for dispatch that allows a thunk action (which is a function) to be passed to dispatch and types the return type of the dispatch call to the return type of said thunk. Only then you are able to actually act on that return type.
This also explains your error:
Property 'type' is missing in type 'AsyncThunkAction
'.
A thunk is a function, not an action object with a type property, so unless you add that overload to your dispatch type, you can never call your dispatch method with a thunk as an argument.
If the problem still persists after that, your middleware option to configureStore might be typed incorrectly. This is a bit tricky, as TypeScript likes to throw away individual types of an array and merge them into one "parent" type. The documentation I linked above should give you the required input (use as const for that array, but if that doesn't work out, I can also take a look - in that case you'd have to paste your configureStore call here.
Hi. Thanks much for help, I tried to follow your guides, but it still doesn't make a difference.
types.d.tsdeclare global {
type Store = { [K in keyof typeof store]: ReturnType<typeof store[K]> }
export type RootState = ReturnType<typeof rootReducer>
export type AppDispatch = typeof store.dispatch
export type RootAction = ReturnType<typeof store.dispatch>
}
store/index.tsimport { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import rootReducer from './root.reducer'
import { routerMiddleware } from 'connected-react-router'
import history from './history'
import logger from 'redux-logger'
import { Middleware } from 'redux'
// rehydrate state on app start
const initialState = {}
// create store
const store = configureStore({
reducer: rootReducer,
preloadedState: initialState,
devTools: true,
middleware: [
routerMiddleware(history),
logger as Middleware,
...getDefaultMiddleware<RootState>()
] as const
})
// export store singleton instance
export default store
const mapDispatchToProps = (dispatch: AppDispatch) =>
bindActionCreators(
{
clearAlert: AlertActions.clear,
setAlert: AlertActions.set,
searchNames: AptActions.searchNames,
push
},
dispatch
)
...
const fetchCompletion = async (name: string) => {
try {
const response = await searchNames(name)
if (AptActions.searchNames.fulfilled.match(response)) {
const names = unwrapResult(response)
setOptions(
names.sort((a: string, b: string) => leven(a, name) - leven(b, name)).slice(0, 5)
)
}
} catch (e) {
setOptions([])
}
setLoading(false)
}
Still the same error...
TS2345: Argument of type 'AsyncThunkAction<string[], string, {}>' is not assignable to parameter of type 'Action<unknown>'. Property 'type' is missing in type 'AsyncThunkAction<string[], string, {}>' but required in type 'Action<unknown>'.
I also tried the next setup:
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, unknown, RootAction>) =>
bindActionCreators(
{
clearAlert: AlertActions.clear,
setAlert: AlertActions.set,
searchNames: AptActions.searchNames,
push
},
dispatch
)
Still, nothing changed.
Can redux-logger be the issue? Their typing is outdated and I'm trying to fix it. https://github.com/DefinitelyTyped/DefinitelyTyped/pull/45018.
Probably, but your as Middleware should usually do the trick. Can you move all of that into a CodeSandbox that I could poke around in?
@msutkowski found your issue: bindActionCreators does not take thunks into account. This is not really a RTK issue, but an incompatiblity between redux and redux-thunk. He really deserves a trophy for that one.

There's no need to use bindActionCreators in the first place, as these two notations are 100% identical:
const mapDispatchToProps = (dispatch: Dispatch<RootAction>) =>
bindActionCreators(
{
clearAlert: AlertActions.clear,
setAlert: AlertActions.set,
searchNames: AptActions.searchNames,
push
},
dispatch
)
can be written as
const mapDispatchToProps = {
clearAlert: AlertActions.clear,
setAlert: AlertActions.set,
searchNames: AptActions.searchNames,
push
}
using the object notation for connect.
So including information from https://react-redux.js.org/using-react-redux/static-typing#inferring-the-connected-props-automatically, this will get it working for you:
diff --git a/app/components/SearchBar/index.tsx b/app/components/SearchBar/index.tsx
index aaefe88..5fa9617 100644
--- a/app/components/SearchBar/index.tsx
+++ b/app/components/SearchBar/index.tsx
@@ -1,7 +1,6 @@
import React, { KeyboardEvent, ChangeEvent, useEffect, useState } from 'react'
-import { bindActionCreators, Dispatch } from 'redux'
-import { connect } from 'react-redux'
+import { connect, ConnectedProps } from 'react-redux'
import { push } from 'connected-react-router'
import { debounce, CircularProgress, TextField } from '@material-ui/core'
@@ -22,18 +21,14 @@ const styles = {
}
}
-const mapDispatchToProps = (dispatch: Dispatch<RootAction>) =>
- bindActionCreators(
- {
- clearAlert: AlertActions.clear,
- setAlert: AlertActions.set,
- searchNames: AptActions.searchNames,
- push
- },
- dispatch
- )
+const mapDispatchToProps = {
+ clearAlert: AlertActions.clear,
+ setAlert: AlertActions.set,
+ searchNames: AptActions.searchNames,
+ push
+}
-type SearchBarProps = ReturnType<typeof mapDispatchToProps>
+type SearchBarProps = ConnectedProps<typeof connector>
const SearchBar: React.FC<SearchBarProps> = ({ setAlert, clearAlert, push, searchNames }) => {
const [open, setOpen] = useState(false)
@@ -141,4 +136,6 @@ const SearchBar: React.FC<SearchBarProps> = ({ setAlert, clearAlert, push, searc
)
}
-export default connect(null, mapDispatchToProps)(SearchBar)
+const connector = connect(null, mapDispatchToProps)
+
+export default connector(SearchBar)
Damn, @msutkowski, @phryneas thank you, guys!
You really did an awesome investigation though I didn't point you where I had this issue, and you did even find where mine code was! Amazing. People like you make me believe in OSS :heart:
Should we contribute to redux to improve bindActionCreators?
I mean, from react-redux docs, they say:
Because this is so common, connect supports an “object shorthand” form for the mapDispatchToProps argument: if you pass an object full of action creators instead of a function, connect will automatically call
bindActionCreatorsfor you internally.
(https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object)
Although my example with bindActionCreators wasn't the best as I didn't really need it, it should have worked, so some typing error takes a place here.
Closing the issue as I have no errors now! :fireworks:
This is something that was apparently taken out of redux somewhere last year. I'm not sure, but realistically, there's no good reason to use bindActionCreators at all any more, so I guess they're just removing it slowly?
Uh, hang on, what?
Nothing has been removed from Redux or React-Redux at all. bindActionCreators is still there.
It's just that from a typing perspective, the React-Redux typings for connect do some specific weird voodoo to "collapse thunks" down to their actual return type from the component's perspective if you're using the object shorthand, but you don't get that same behavior (I think) if you're using mapDispatch as a function and call bindActionCreators yourself.
@markerikson
According to https://github.com/piotrwitek/react-redux-typescript-guide#typing-connected-component, this should have actually been working a year ago. (@msutkowski found that link)
WARNING: As of now (Apr 2019) bindActionCreators signature of the latest redux-thunk release will not work as below, you need to use updated type definitions that you can find here /playground/typings/redux-thunk/index.d.ts and then add paths overload in your tsconfig like this: "paths":{"redux-thunk":["typings/redux-thunk"]}.
So I assumed that this (explicit typing, not bindActionCreators itself) was just being faded out of redux.
tbh the thunk types are kind of in limbo atm. I think there's some changes in master that never got released. Tim has said he wants to hold off on that until Redux 5.0 (the TS conversion) is ready, but he's also been not really making any push to get that done either (part of why I've poked you to see if you had any thoughts on those types).
Yeah, I'm really good at getting distracted lately :/ It's still on my list.
C'MON, LENZ, DO THAT TYPESCRIPT MAGIC FASTER!
cracks whip
Sorry, got distracted trying to find a perfect new typing for bindActionCreator. :laughing:
No success so far though. Not sure if it's possible.
https://twitter.com/phry/status/1265399522127155213

Most helpful comment
@msutkowski found your issue:
bindActionCreatorsdoes not take thunks into account. This is not really a RTK issue, but an incompatiblity betweenreduxandredux-thunk. He really deserves a trophy for that one.There's no need to use
bindActionCreatorsin the first place, as these two notations are 100% identical:can be written as
using the object notation for connect.
So including information from https://react-redux.js.org/using-react-redux/static-typing#inferring-the-connected-props-automatically, this will get it working for you: