0

I was following along with the Redux Essentials Tutorials on how to employ createAsyncThunk for generating Thunks. In their example here they create a thunk like so:

export const addNewPost = createAsyncThunk(
  'posts/addNewPost',
  async (initialPost) => { // Note: initialPost is an object with 3 props: title, content, user
    const response = await client.post('/fakeApi/posts', initialPost)
    return response.data
  }
)

and they call it in another file like this:

await dispatch(addNewPost({ title, content, user: userId }))

In my project I've installed typescript and react types (@types/react). Even though the code is JavaScript, this gives me intellisense from VSCode IDE on proper typings. However when I do above I see:

typing error about 0 arguments expected

The typings expects 0 arguments but gets one, my object. Hovering over the method addNewPost I see that it takes no args and returns the async action.

0 args for function

How can I have my IDE and typescript typings support recognize the the proper params required?

What I tried

Tried to add some JSDOC string to the created addNewPost function like so:

/**
 * addNewPost
 * @returns {(initialPost:{title:string, content:string, user: string}) => void} the returned action create takes an object as arg
 */
export const addNewPost = createAsyncThunk(
  'posts/addNewPost',
  async (initialPost) => {
    const response = await client.post('/fakeApi/posts', initialPost)
...

Following another Stack Overflow suggestion on how to use JSDocs to describe a returned function. but that doesn't seem to work.

Anyone have any suggestions?

Ryan Southcliff
  • 143
  • 2
  • 12

2 Answers2

1

The issue is that the default Dispatch type from Redux only understands that the dispatch() function can accept plain action objects. It does not know that a thunk function is a valid thing that can be passed in.

In order to make this code work correctly, you need to follow our instructions for setting up the store and inferring the real type of dispatch based on all the actual middleware that were configured, which would usually include the thunk middleware:

https://redux.js.org/tutorials/typescript-quick-start

Then, use that AppDispatch type elsewhere in the app, so that when you do try to dispatch something TS recognizes that thunks are a valid thing to pass in.

So, typically:

// store.ts
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

// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

// MyComponent.ts
import { useAppDispatch } from '../../app/hooks'

export function MyComponent() {
  const dispatch = useAppDispatch()

  const handleClick = () => {
    // Works now, because TS knows that `dispatch` is `AppDispatch`,
    // and that the type includes thunk handling
    dispatch(someThunk())
  }
}

Additionally, in your specific case, you're using createAsyncThunk. You need to tell TS what the types are for its arguments, per https://redux-toolkit.js.org/usage/usage-with-typescript#createasyncthunk:

export const addNewPost = createAsyncThunk(
  'posts/addNewPost',
  async (initialPost: InitialPost) => { 
    // The actual `client` in the Essentials tutorial is plain JS
    // But, if we were using Axios, we could do:
    const response = await client.post<ResultPost>('/fakeApi/posts', initialPost)
    // If the `client` was written well, `.data` is of type `ResultPost`
    return response.data
  }
)
markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Thank you. I'm not using typescript of course but I've found so far that besides creating one .ts file for my root state and dispatch types as you mention and per [docs](https://react-redux.js.org/using-react-redux/usage-with-typescript#define-root-state-and-dispatch-types) the trick is to essentially follow the typescript guide but replace _ts types_ with _JSDoc params_. – Ryan Southcliff Apr 21 '22 at 05:39
  • Yeah, if you're trying to specify types via JSDoc, you are essentially "using TS", just not as a `.ts` file with actual TypeScript syntax. – markerikson Apr 21 '22 at 15:08
0

I had some luck with revising the JSDoc to be specifically above the payloadCreator param inside the createAsyncThunk function like so:

export const addNewPost = createAsyncThunk(
  'posts/addNewPost',
  /**
   * Make POST request to API w. params and create a new record
   * @param {{content: string, title: string, user: string}} initialPost
   * @returns {Promise<{content: string, date: string, id: string, reactions: Object, title: string, user: string}>} returned data
   */
  async (initialPost) => {
    const response = await client.post('/fakeApi/posts', initialPost)
    // The response includes the complete post object, including unique ID
    return response.data
  }
)

As I look more closely at how createAsyncThunk works I realize why this makes more sense as createAsyncThunk itself is not returning these values, they are params and return types of the functions we are passing to it.

Ryan Southcliff
  • 143
  • 2
  • 12