Typing Redux Thunk with middleware in TypeScript: A comprehensive guide

I recently integrated Redux into my SPFx webpart (using TypeScript), and everything seems to be working fine. However, I'm struggling with typing the thunk functions and could use some guidance on how to properly type them.

Here's an example of the action I'm trying to dispatch:

  export const setListByMiddelware:any = (listId:string) => (dispatch, getState) => {
    dispatch(listIdAdded(listId));
    dispatch(spApiCallBegan({
        method: GET_LIST_ITEMS,
        options: {
            listId
        },
        onSuccess: itemsAdded.type
    }));
  }

Additionally, here is the SharePoint middleware responsible for making the SP call:

import SP from '../../services/SharePointService';
import * as actions from '../SP_API';

const api = store => next => async action => {
    if(action.type !== actions.spApiCallBegan.type) {
        next(action);
        return;
    }

    const { method, options, onSuccess, onError, onStart } = action.payload;

    if(onStart)  {
        store.dispatch({ type: onStart });
    }

    next(action);

    try {
        const result = await SP[method](options);

        store.dispatch(actions.spApiCallSuccess(result));
        if(onSuccess) {
            store.dispatch({
                type: onSuccess,
                payload: result
            });
        }
    } catch(ex) {
        store.dispatch(actions.spApiCallFailed(ex.message));
        if(onError) {
            store.dispatch({ 
                type: onError,  
                payload: ex.message 
            });
        }
    }
}

export default api;

While I've managed to make things work by simply typing the thunks as any, I'd prefer to strongly type them since they play a crucial role in my components.

If you're curious about the tools I'm using: [email protected]
[email protected]
@reduxjs/[email protected]

Despite my efforts, I have encountered difficulties in typing Redux elements, as evidenced by the struggles described above. Any advice or resources would be greatly appreciated.

For TypeScript specifics, I believe I am currently utilizing TS 3.3 within SPFx version 1.11.0.

Edit
After consulting with markerikson, I have explored the official Redux documentation on typing with TypeScript, which can be found here: https://redux.js.org/recipes/usage-with-typescript#type-checking-middleware

However, despite following the suggested implementations, I encountered errors such as: Type '(listId: string) => (dispatch: any, getState: any) => void' is not assignable to type 'ThunkAction<void, IListSate, unknown, AnyAction>'. Types of parameters 'listId' and 'dispatch' are incompatible. Type 'ThunkDispatch<IListSate, unknown, AnyAction>' is not assignable to type 'string'. regarding the thunk function and 'api' is referenced directly or indirectly in its own type annotation. related to the middleware.

Below is the configuration of my store:

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import listRedurcer, { IListSate} from './lists';
import SP_API from './middleware/SP_API';


const store = configureStore({
    reducer: listRedurcer,
    middleware: [
        ...getDefaultMiddleware(),
        SP_API
    ],
    devTools : {
        name: 'ReduxList'
    }
});

// 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;

export default store;

In attempting to address the thunk issue, I also experimented with

ThunkAction<void, RootState, string, AnyAction>
but encountered the same error message.

Answer №1

The Redux documentation for "Usage with TypeScript" specifically delves into how to type middleware and thunks:

When creating a custom middleware, the code would resemble:

import { Middleware } from 'redux'

import { RootState } from '../store'

export const exampleMiddleware: Middleware<
  {}, // Most middleware do not modify the dispatch return value
  RootState
> = storeApi => next => action => {
  const state = storeApi.getState() // correctly typed as RootState
}

For handling thunks, consider the following code snippet:

import { AnyAction } 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, AnyAction> => async dispatch => {
  const asyncResp = await exampleAPI()
  dispatch(
    sendMessage({
      message,
      user: asyncResp,
      timestamp: new Date().getTime()
    })
  )
}

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

Update

If you are attempting to use ThunkAction in this manner:

export const setListByMiddelware : ThunkAction = (listId:string) => (dispatch, getState) => {}

This approach is incorrect. setListByMiddelware does not act as a ThunkAction itself - it actually produces a ThunkAction. The correct usage would be:

export const setListByMiddelware = (listId:string) : ThunkAction => (dispatch, getState) => {}

Furthermore, ensure that your middleware parameter within the store configuration is properly defined:

  • The middleware parameter should accept getDefaultMiddleware as an argument callback
  • You need to invoke it and utilize the array's prepend and concat methods for configuring accurate types:
const store = configureStore({
    reducer: listRedurcer,
    middleware: getDefaultMiddleware => getDefaultMiddleware().concat(SP_API),
    devTools : {
        name: 'ReduxList'
    }
});

Refer to https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type

Answer №2

ThunkAction<void, RootState, string, AnyAction>
might be suitable if you have created your thunk all at once, like
setListByMiddelware=(dispatch, getState, listId:string)=>{}
, however, setListByMiddelware is not exactly a thunk itself, but more of a thunk factory. In order to accurately reflect its type, consider using:

const setListByMiddelware:(listId:string)=>ThunkAction<void, RootState, never, AnyAction> = 
  (listId:string) => (dispatch, getState) => {

Answer №3

'api' is mentioned directly or indirectly within its own type annotation.

This error occurs due to the interdependence between the middleware type and the RootState type, which is derived from the store. This creates a circular typing issue as the store type relies on the middleware and vice versa.

To resolve this, one solution is to define the RootState type without involving the store. If your entire reducer is listReducer, you can achieve this by:

export type RootState = ReturnType<typeof listReducer>;

This approach will align with the api type recommended by @markerikson

const api: Middleware<{}, RootState> = store => next => async action => {

The function setListByMiddleware has a return type of ThunkAction, but the function itself is not a ThunkAction.

export const setListByMiddelware = (
  listId: string
): ThunkAction<void, RootState, never, AnyAction> => (dispatch, getState) => {
  dispatch(listIdAdded(listId));
  dispatch(
    spApiCallBegan({
      method: GET_LIST_ITEMS,
      options: {
        listId
      },
      onSuccess: itemsAdded.type
    })
  );
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Clickable Angular Material card

I am looking to make a mat-card component clickable by adding a routerlink. Here is my current component structure: <mat-card class="card" > <mat-card-content> <mat-card-title> {{title}}</mat-card-title> &l ...

Exploring the best practices for utilizing the error prop and CSS with the Input API in Material UI while utilizing context

When working with the MUI Input API props and CSS, I encountered an issue related to the {error} and its use. This is how my code looks: const [value, setValue] = useState<string>(cell.value); const [startAdornment, setStartAdornment] = useState< ...

What is the correct way to trigger an event specified as a string parameter in the emit() function?

My current goal is to pass the emit name as a string (for example, 'showComponent') from child to parent. I then want to trigger another emit in the emitAction(callbackName: string) function, and finally execute the showComponent() function. I&a ...

How to Extract the Specific Parameter Type from a Function in Typescript

After generating a client for an API using typescript-node, I encountered the following code: export declare class Api { getUser(username: string, email: string, idType: '1298' | '2309' | '7801') } I need to access the ...

The continuous re-rendering is being triggered by the Async/Await Function

I am facing an issue with fetching data from the backend using axios. The function is returning a Promise and each time I call it, my component keeps rendering continuously. Below is the code snippet: import { useState } from "react"; import Ax ...

Tips on resolving the 404 path error in Angular2/TypeScript ASP.NET 4.6.1 on Visual Studio 2015

I'm facing a challenge while developing a new application using TypeScript, Angular2, and ASP.NET 4.6.1 on VS2015. Two issues have come up in the process. First problem: I keep encountering 404 errors with the include files in my index.html file. Upo ...

The mysterious appearance of the <v-*> custom element in Vuetify Jest

Currently, I am in the process of writing unit tests for my project using Jest. The project itself is built on Vue, Vuetify (1.5), TypeScript, and vue-property-decorator. One particular area of focus for me has been creating a basic wrapper for the <v- ...

Increasing a number after a delay in an Angular 2 AppComponent using TypeScript

I'm attempting to create a straightforward Angular2 Application with TypeScript. Despite its apparent simplicity, I'm struggling to achieve my desired outcome. My goal is to display a property value in the template and then update it after 1 sec ...

Troubleshooting the issue with mocking the useTranslation function for i18n in JEST

Currently, I am facing an issue with my react component that utilizes translations from i18next. Despite trying to create tests for it using JEST, nothing seems to be getting translated. I attempted to mock the useTranslation function as shown below: cons ...

Issue: Inadequate parameters have been supplied for the localized pathname (getPathname()). This problem arose after the inclusion of "next-intl/routing" module

After implementing the config file and replacing : Navigation File import { createLocalizedPathnamesNavigation, Pathnames } from 'next-intl/navigation'; With : Config File import {Pathnames, LocalePrefix} from 'next-intl/routing';} ...

What does the typeof keyword return when used with a variable in Typescript?

In TypeScript, a class can be defined as shown below: class Sup { static member: any; static log() { console.log('sup'); } } If you write the following code: let x = Sup; Why does the type of x show up as typeof Sup (hig ...

The Angular4 HTTP POST method fails to send data to the web API

Whenever I make a http post request, it keeps returning an error message saying "data does not pass correctly". I have tried passing the data through the body and also attempted using json.stringify(). Additionally, I experimented with setting the content ...

Implement TypeScript to include type annotations on functions' parameters using destructuring and the rest syntax

Issues with Typing in Typescript Trying to learn typing in Typescript has presented some difficulties for me: I am struggling to convert this code into a strongly-typed format using Typescript. const omit = (prop: P, { [prop]: _, ...rest}) => rest; ...

What is the best way to access the data being sent to a router link in Angular?

After navigating to the user page using the router sample below, how can I retrieve the details once it reaches the user page? I need to verify in my user.component.ts that the navigation is triggered by this [routerLink]="['user', user.id, &apo ...

Experiencing a bug in my express application: TypeError - Unable to access properties of undefined (reading 'prototype')

I've encountered the TypeError: Cannot read properties of undefined (reading 'prototype') error in my javascript file. Despite researching online, it seems that many attribute this issue to importing {response} from express, but I am not doi ...

Locate the minimum and maximum values between two inputted dates

I'm looking for a solution that provides strongly typed code. The problem arises when trying to implement solutions from a related question - Min/Max of dates in an array? - as it results in an error. TS2345: Argument of type 'Date' is not ...

What is the best way to interact with and modify the relationships of "many-to-many" fields in my database table?

As someone who is new to nestjs, I am working with two entities: @Entity({ name: "Books" }) @ObjectType() export class Book { @PrimaryGeneratedColumn() @Field() id: number; @Column() @Field() title: string; @ManyToMany(() => Auth ...

After compilation, what happens to the AngularJS typescript files?

After utilizing AngularJS and TypeScript in Visual Studio 2015, I successfully developed a web application. Is there a way to include the .js files generated during compilation automatically into the project? Will I need to remove the .ts files bef ...

Ways to steer clear of utilizing subscriptions and BehaviorSubject.value through a declarative method within rxjs

As I refactor my Angular application, my goal is to eliminate all subscriptions and rely solely on the async pipe provided by Angular for a declarative approach instead of an imperative one. I encounter difficulties implementing a declarative approach whe ...

Struggling to set up the connection between React-Redux connect and the Provider store

Currently utilizing Redux with React Native for state management. I've set up the store and Provider successfully it seems, as I can utilize store.getState() and store.dispatch(action()) from any component without issue. However, I'm encountering ...