Search code examples
reactjstypescriptreduxredux-toolkit

How to correctly set the Typescript type of the result returned by the dispatch method in redux?


fetchSomething is defined like this:

export const fetchSomething = createAsyncThunk(
  'something',
  async () => {
    return Promise.resolve({name: 'jack'})
  }
)

Here's the code inside the React component:

const dispatch = useAppDispatch()

const getMessages = async () => {
  const result = await dispatch(fetchSomething())
  return result.payload
}

result has the correct Typescript type, while result.payload is of type unknown

const result: PayloadAction<{name: string}, string, {arg: void, requestId: string, requestStatus: "fulfilled"}, never> | PayloadAction<unknown, string, {arg: void, requestId: string, requestStatus: "rejected", aborted: boolean, condition: boolean}, SerializedError>

I found related information on official documentation, but I don't know how to use in my example:

import { UnknownAction } from 'redux'
import { sendMessage } from './store/chat/actions'
import { RootState } from './store'
import { ThunkAction } from 'redux-thunk'

export const thunkSendMessage =
  (message: string): ThunkAction<void, RootState, unknown, UnknownAction> =>
  async dispatch => {
    const asyncResp = await exampleAPI()
    dispatch(
      sendMessage({
        message,
        user: asyncResp,
        timestamp: new Date().getTime()
      })
    )
  }

function exampleAPI() {
  return Promise.resolve('Async Chat Bot')
}

I want the getMessages method to return the correct type Promise<{name: string}>

I queried the docs again as suggested by @timotgl, found this section, and made changes to the code, but the problem persists, The type of result.payload is still unknown.

export const fetchSomething = createAsyncThunk<{name: string}>(
  'something',
  async () => {
    return Promise.resolve({name: 'jack'})
  }
)

Solution

  • See Basic createAsyncThunk Types:

    In the most common use cases, you should not need to explicitly declare any types for the createAsyncThunk call itself.

    Just provide a type for the first argument to the payloadCreator argument as you would for any function argument, and the resulting thunk will accept the same type as its input parameter. The return type of the payloadCreator will also be reflected in all generated action types.

    interface MyData {
      // ...
    }
    
    const fetchUserById = createAsyncThunk(
      'users/fetchById',
      // Declare the type your function argument here:
      async (userId: number) => {
        const response = await fetch(`https://reqres.in/api/users/${userId}`)
        // Inferred return type: Promise<MyData>
        return (await response.json()) as MyData // <--
      }
    )
    
    // the parameter of `fetchUserById` is automatically inferred to `number` here
    // and dispatching the resulting thunkAction will return a Promise of a correctly
    // typed "fulfilled" or "rejected" action.
    const lastReturnedAction = await store.dispatch(fetchUserById(3))
    

    Take note of the thunk's return value:

    // Inferred return type: Promise<MyData>
    return (await response.json()) as MyData
    

    Applying the above suggested pattern to your code:

    interface Something {
      name: string;
    };
    
    export const fetchSomething = createAsyncThunk(
      'something',
      async () => {
        return Promise.resolve({ name: 'jack' }) as Something;
      }
    )
    
    const dispatch = useAppDispatch()
    
    const getMessages = async () => {
      try {
        const result = await dispatch(fetchSomething()).unwrap();
        return result;
      } catch(error) {
        // ... any error handling logic ...
      }
    };