redux-toolkit: Can't reproduce createAsyncThunk() example.

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.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17 (13 by maintainers)

Commits related to this issue

Most upvoted comments

@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.

trophy

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)

C’MON, LENZ, DO THAT TYPESCRIPT MAGIC FASTER!

cracks whip

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 ❤️

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 bindActionCreators for 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! 🎆

Sorry, got distracted trying to find a perfect new typing for bindActionCreator. 😆 No success so far though. Not sure if it’s possible. https://twitter.com/phry/status/1265399522127155213

distraction