Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use with TypeScript 2.0 (@types/redux-thunk) #103

Closed
dupski opened this issue Oct 11, 2016 · 47 comments
Closed

Use with TypeScript 2.0 (@types/redux-thunk) #103

dupski opened this issue Oct 11, 2016 · 47 comments

Comments

@dupski
Copy link

dupski commented Oct 11, 2016

Hi

I'm attempting to use redux-thunk with TypeScript 2.0. The definitions from npm install @types/redux-thunk is as follows:

import * as Redux from "redux";
import { Middleware } from "redux";

export as namespace ReduxThunk;

declare const thunk: Middleware & {
    withExtraArgument(extraArgument: any): Middleware;
};
export default thunk;

declare module 'redux' {
    export type ThunkAction<R, S, E> = (dispatch: Dispatch<S>, getState: () => S, extraArgument: E) => R;

    export interface Dispatch<S> {
        <R, E>(asyncAction: ThunkAction<R, S, E>): R;
    }
}

The above appears to be completely different the one in the repo ?

I'm creating my store like this:

    var middleware = [
        thunk
    ];

    var store = createStore(
        combineReducers({
            things1: Reducer1,
            things2: Reducer2
        }),
        initialState,
        compose(
            applyMiddleware(...middleware),
            devToolsAvailable() ? window.devToolsExtension() : f => f
        )
    );

Then trying to initiate a thunk like this (I've copied the body of the function into the dispatch call for simplicity, it is returned from an action creator in my real code):

store.dispatch((dispatch: Redux.Dispatch<State>, getState: () => State) => {
        if (!getState().things1.loading) {
            dispatch(loadStart());
            loadStaticData('things.json', {json: true})
                .then((data: Things[]) => {
                    dispatch(loadComplete(data));
                })
                .catch((err) => {
                    dispatch(loadFailed(err));
                });
        }
    })

Unfortunately the error I get back is:

[ts] Argument of type '(dispatch: Dispatch<State>, getState: () => State) => void' is not assignable to parameter of type 'Action'.
       Property 'type' is missing in type '(dispatch: Dispatch<State>, getState: () => State) => void'.

So it looks like the defs available through npm aren't working (or I am missing something!).

Please could someone confirm:

  • Are the typings available from npm current?
  • and if so, what am I missing in my example above?

Thanks guys!!

@dupski
Copy link
Author

dupski commented Oct 11, 2016

I have just realised that I could avoid this whole issue by just calling store.dispatch in my async function :P But it would be good to know what is wrong with the typings / my code. Thanks! :)

@baszalmstra
Copy link

I'm running into the same issue.

@Q-Man
Copy link

Q-Man commented Nov 15, 2016

I also don't get the redux-thunk typings to work or just don't get the point.

The Dispatch-Definition enforces you to dispatch a ThunkAction. But in Redux with Redux-Thunk Middleware you can either dispatch a classic action (object with ha type property) or a ThunkAction, so the Dispatch typing should support both cases, shouldn't it?

@jbcazaux
Copy link

I have no problem with typescript 2 and @types/redux-thunk since the begining, but I had troubles with the typings definitions when I was using Typescript 1.8.

I m not sure to to it the right way, but it works for me (i dont use extra arguments):

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {reducer} from './reducers/index';
import {ShoppingList} from './shoppingList';
import {createStore, applyMiddleware, ThunkAction } from 'redux';
import thunk from 'redux-thunk';
import {State} from './reducers/state';

const store = createStore(
    reducer,
    applyMiddleware(thunk)
);

type ThunkAction2<R> = ThunkAction<R, State, void>; // you can inline that
export type ThunkActionCreator<R> = (...args: any[]) => ThunkAction2<R>; 

ReactDOM.render(
    <Provider store={store}>
        <ShoppingList title="liste de courses"/>
    </Provider>,
    document.getElementById("app")
);

You can see a simple example here
index.tsx
some actions

Please tell me if it helps or if i m doing wrong !

@Q-Man
Copy link

Q-Man commented Nov 16, 2016

In your example you didn't type the dispatch parameter in your thunk action creator.

Anyway, I got it working. The problem was my IDE. IntelliJ IDEA doesn't recognize the multiple typings for Dispatch (1 from the redux npm module for classic actions and 1 from the redux-thunk typings for thunk actions). So the IDE showed errors even though the build succeeded.

@jbcazaux
Copy link

Oh ok I understand what you mean, indeed I have the same error.

@Q-Man
Copy link

Q-Man commented Nov 16, 2016

As a workaround I made an own type definition file that doesn't cause IDEA to show errors for wrong parameters at Dispatch:

declare module "redux-fixed" {
  export type ThunkAction<R, S, E> = (dispatch: Dispatch<S>, getState: () => S, extraArgument: E) => R;

  export interface Dispatch<S> {
    <R, E>(asyncAction: ThunkAction<R, S, E>): R;
  }
  export interface Dispatch<S> {
    <A>(action:A &{type:any}): A &{type:any};
  }

//compiler compiles correctly, but IDEA shows error if original Dispatch from redux bundle is used:
/* Original:
 export interface Dispatch<S> {
     <A extends Action>(action: A): A;
 }
 */
    }

In my application I'm importing Dispatch from "redux-fixed" now, that passes the IDEA error checks.

@ritwik
Copy link

ritwik commented Nov 22, 2016

I have the same problem, and my build isn't succeeding unlike @Q-Man 's (i.e, it's not an editor problem - my editor, vscode, and build report the same issue).

@lanceschi
Copy link

lanceschi commented Nov 22, 2016

Hi,

I'm working with typescript@2.0.3 on vscode and I've encountered the same error report mentioned by @dupski launching an async (redux-thunk) action. Here's the error:

Argument of type '(dispatch: Function, getState: Function) => void' is not assignable to parameter of type 'Action'.
  Property 'type' is missing in type '(dispatch: Function, getState: Function) => void'.

... and here's the action function code (typescript annotations are purposely very lousy):

export const loadList = () => {
  return (dispatch:Function, getState:Function) => {
    const state = getState(),
          xhrFilterParams = {
            xy: state.list.xy,
            uv: state.list.uv
          };

    Api.getList(xhrFilterParams).then((response:any) => {
      dispatch(setItems(response));
    }).catch((error:any) => {
      console.error(error);
    });
  }
}

It seems to me that the issue is related to Dispatch overloading as brilliantly exposed in #82 by @KingHenne. My dirty workaround is to add the :any annotation to the expected return of loadList function. Here's the full code:

export const loadList = ():any => {
  return (dispatch:Function, getState:Function) => {
    const state = getState(),
          xhrFilterParams = {
            xy: state.list.xy,
            uv: state.list.uv
          };

    Api.getList(xhrFilterParams).then((response:any) => {
      dispatch(setItems(response));
    }).catch((error:any) => {
      console.error(error);
    });
  }
}

Any comments are welcomed and appreciated!

@jbcazaux
Copy link

I think this is the same workaround as mine, but as @Q-Man said, it works because we cast dispatch as Function.
I prefere to return a promise from my asyncActionCreators, any is not cool :)

@lanceschi
Copy link

lanceschi commented Nov 22, 2016

@jbcazaux thanks, I got your point! Following your insight and since I'm not returning anything a better code solution in my case would be a :void annotation:

export const loadList = () => {
  return (dispatch:Function, getState:Function):void => {
    const state = getState(),
          xhrFilterParams = {
            xy: state.list.xy,
            uv: state.list.uv
          };

    Api.getList(xhrFilterParams).then((response:any) => {
      dispatch(setItems(response));
    }).catch((error:any) => {
      console.error(error);
    });
  }
}

I'm still in my Typescript early days 😉

@Q-Man
Copy link

Q-Man commented Nov 22, 2016

I got it working with typed ThunkActions with my solution of adding an own type definition file for the Dispatch Definitions:

redux-fixed/index.d.ts:

//Fix for problem that TypeScript doesn't accept Dispatch from redux npm bundle
declare module "redux-fixed" {
  export type ThunkAction<R, S, E> = (dispatch: Dispatch<S>, getState: () => S, extraArgument: E) => R;

  export interface Dispatch<S> {
    <R, E>(asyncAction: ThunkAction<R, S, E>): R;
  }
  export interface Dispatch<S> {
    <A>(action:A &{type:any}): A &{type:any};
  }
}

actions.ts:

/// <reference path="redux-fixed/index.d.ts" />
import {ThunkAction, Dispatch} from 'redux-fixed';
import {IMainState} from "./store-state";
import {Action} from "redux";


type ThunkInterface<R> = ThunkAction<R, IMainState, void>;
type ReduxDispatch = Dispatch<IMainState>;

interface IGetState {
	():IMainState;
}

interface ReduxAction extends Action {
    payload?: any;
    error?: boolean;
    meta?: any;
    errorMessage?: string;
}

function dummyThunkActionCreator():ThunkInterface<string> {
	return (dispatch:ReduxDispatch, getState:IGetState) => {
		dispatch(dummyActionCreator("dummy"));
		return "dummyReturnValue";
	}
}


function dummyActionCreator(anyParam:string):ReduxAction {
	return {
		type: "dummyActionType",
		payload: anyParam,
	}
}

@born2net
Copy link

born2net commented Dec 1, 2016

I am still getting the same error:

Error:(13, 29) TS2345:Argument of type '(dispatch: any) => void' is not assignable to parameter of type 'Action'.
  Property 'type' is missing in type '(dispatch: any) => void'.

with latest @type/redux-thunk

any ideas?

regards

Sean

@lanceschi
Copy link

@born2net could you please share the function code?

@aikoven
Copy link
Collaborator

aikoven commented Dec 1, 2016

Until #77 is released, you can just copy the definition from the PR to your project and then tell TS how to find it using path mapping:

// typings.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "redux-thunk": ["path/to/definition"]
    }
  }
}

@born2net
Copy link

born2net commented Dec 1, 2016

I removed redex-thunk from my typings, and added this to my typings.d.ts and still same issue :(

declare module 'redux-thunk' {
  import { Middleware } from 'redux';
  const thunkMiddleware : Middleware;
  export default thunkMiddleware;
  export type ThunkAction<R, S, E> = (dispatch: Dispatch<S>, getState: () => S, extraArgument: E) => R;
  export interface Dispatch<S> {
    <R, E>(asyncAction: ThunkAction<R, S, E>): R;
  }
  export interface Dispatch<S> {
    <A>(action:A &{type:any}): A &{type:any};
  }
}

I know it's being applied since if I remove the entire snippet I get many errors on redux, so it is taking it, its just that it does not satisfy this issue of:

Error:(13, 29) TS2345:Argument of type '(dispatch: any) => void' is not assignable to parameter of type 'Action'.
  Property 'type' is missing in type '(dispatch: any) => void'.

regards

Sean

@born2net
Copy link

born2net commented Dec 1, 2016

here is my latest and still same issue:

declare module 'redux-thunk' {
  import { Middleware } from 'redux';
  const thunkMiddleware : Middleware;
  export default thunkMiddleware;
  export type ThunkAction<R, S, E> = (dispatch: Dispatch<S>, getState: () => S, extraArgument: E) => R;
  export interface Dispatch<S> {
    <R, E>(asyncAction: ThunkAction<R, S, E>): R;
  }
  export interface Dispatch<S> {
    <A>(action:A &{type:any}): A &{type:any};
  }
}

error:

Error:(13, 29) TS2345:Argument of type '(dispatch: any) => void' is not assignable to parameter of type 'Action'.
  Property 'type' is missing in type '(dispatch: any) => void'.

@aikoven
Copy link
Collaborator

aikoven commented Jan 18, 2017

Released v2.2.0 with TypeScript definitions included.

@cr0cK
Copy link

cr0cK commented Apr 4, 2017

@Q-Man Have you an example on how you use your typed thunk actions in a component with a mapDispatchToProps + bindActionCreators ?

I managed to type the thunk actions in my actions module like you but I can't bind them into a mapDispatchToProps correctly. Still have issues because the action is known as a ThunkAction that doesn't match when I'm writing this.props.myThunkAction().then((results) => {} );

@mariusGundersen
Copy link

Am I misunderstanding something, or is it a bit weird that both dispatch and getState use the same generic type? I don't want to dispatch my entire state, right? I want to dispatch an action, with a type parameter, but I don't want my state to have type parameter, right? Shouldn't it be

export type ThunkAction<R, S, A, E> = (dispatch: Dispatch<A>, getState: () => S,
                                extraArgument: E) => R;

@jaeeunl
Copy link

jaeeunl commented May 1, 2017

Having the same issue
app/_dashboard.store.ts(54,37): error TS2345: Argument of type '(dispatch: any) => void' is not assignable to parameter of type 'Action'.

the line:

 `DashboardStore.dispatch((dispatch) => {
                if (response.data) {
                    let notifications = response.data;
                    delete notifications.ResultCode;
                    dispatch({
                        type: 'STORE_NOTIFICATIONS',
                        notifications: notifications
                    });
                }
            });`

@dupski
Copy link
Author

dupski commented May 2, 2017

Hey guys, I've now had way more experienced with TS and have worked out a good way to use the typings that ship with redux-thunk...

Basically the trick is to define your thunk function as a ThunkAction.

The ThunkAction<R, S, E> type is a generic type that takes 3 properties:

Heres an example:

import { Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { getStore, IState } from './my_store';

let store = getStore();

// Create myThunkAction function with a type of ThunkAction<R, S, E>
let myThunkAction: ThunkAction<Promise<string>, IState, null> =
    (dispatch: Dispatch<IState>, getState: () => IState) => {
        return new Promise<string>((resolve, reject) => {

            // do async stuff with getState() and dispatch(), then...
            resolve('done!');

        });
    }

store.dispatch(myThunkAction)
.then(() => {
    // do stuff after the thunk has finished...
});

Hope this helps folks. Closing this issue now :)

@dupski dupski closed this as completed May 2, 2017
@dupski
Copy link
Author

dupski commented May 2, 2017

Heres an example thunk that takes some arguments:

// Return a thunk with a type of ThunkAction<R, S, E>
function myThunkAction(someArg: string): ThunkAction<Promise<string>, IState, null> {
    return (dispatch: Dispatch<IState>, getState: () => IState) => {
        return new Promise<string>((resolve, reject) => {

            // do async stuff with someArg, getState() and dispatch(), then...
            resolve('done!');

        });
    };
}

store.dispatch(myThunkAction('www.google.com'))
.then(() => {
    // do stuff after the thunk has finished...
});

@cr0cK
Copy link

cr0cK commented May 2, 2017

@dupski You don't need to type Dispatch and getState, it's already done par ThunkAction.

See:

It improves readability :)

@andrerpena
Copy link

It took me hours to solve it.

In my case, the problem was that Dispath<S> exists in both redux and react-redux. The type definitions from redux-thunk create an overload for the Dispatch<S> that exists in redux, not react-redux.

@sarink
Copy link

sarink commented Dec 12, 2017

@cr0cK Were you ever able to get this to work with mapDispatchToProps? What did you do?'

Example:

import { ThunkAction } from 'redux-thunk';

interface IState {}
interface IExtraArgument { apiClient: any; }
export interface ILoadItem {
  (id:string) : ThunkAction<Promise<any>, IState, IExtraArgument>;
}
export const loadItem:ILoadItem = (id:string) => {
  return (dispatch, getState, { apiClient }) => {
    const startAction = { type: 'LOAD_ITEM_START', payload: { id } };
    dispatch(startAction);
    return apiClient.get('').then((response:any) => {
      const successAction = { type: 'LOAD_ITEM_SUCCESS', payload: { id, response } };
      return dispatch(successAction);
    }, (error:any) => {
      const failAction = { type: 'LOAD_ITEM_FAIL', payload: { id, error } };
      return dispatch(failAction);
    });
  };
};

this works and is completely type-safe

However, when you try to map this in a container component:

import React from 'react';
import { connect } from 'react-redux';
import { ILoadItem } from 'itemActions';

interface IProps {
  loadItem: ILoadItem;
}

class AccountsContainer extends React.Component<IProps, {}> {
  componentDidMount() {
    this.props.loadItem(1).then((action) => {
      // TS doesn't realize that this is the "wrapped" version of the thunk, and that it returns a Promise.
      // TS thinks that the `loadItem` call will return a thunk
    });
  }
  
  render() {
    return <div></div>;
  }
}

const mapStateToProps = (state:{}) : IStoreProps => ({});

const mapDispatchToProps:IDispatchProps = {
  loadItem,
};

export default connect<IStoreProps, IDispatchProps>(mapStateToProps, mapDispatchToProps)(AccountsContainer);

@cr0cK
Copy link

cr0cK commented Dec 12, 2017

@sarink Yes, you can't use the same props ILoadItem in your component because the return value is not the same. To handle this, you can declare a type/interface for your actions by using a generic type in order to "configure" the type of the returned value, according to the usage context. See this example:

/** action.js **/

import { ThunkAction } from 'redux-thunk';

export interface IItem {
  id: string;
}

// use a generic to define the type of the return according to the context (binded action or not)
export type LoadItem<R> = (id: string) => R;

// thunk action with the correct return type
export const loadItem: LoadItem<ThunkAction<<Promise<IItem>, IState, IExtraArgument>>> = (id) => {
  return (dispatch, getState) => {
    // ...
  }
};

/** MyComponent.tsx */

// split your props in order to reuse them
// in mapStateToProps and mapDispatchToProps
export interface IStateProps {
  // ...
}

export interface IDispatchProps {
  loadItem: LoadItem<Promise<IItem>>;
}

class MyComponent extends React.Component<IProps, {}> {
  componentDidMount() {
    // the action argument should be checked as a string and item should be an `IItem` object
    this.props.loadItem('1').then((item) => { 
      // ...
    });
  }

  render() {
    return <div></div>;
  }
}

/** types.d.ts */

// Mapped type (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#mapped-types)
// Use this to check that all functions have been declared in your mapDispatchToProps according
// to the *DispatchProps of your components
declare type ActionCreatorsMapObject<T> = {
  [K in keyof T]: any;
}

/** index.js **/

import { IDispatchProps } from './MyComponent';

const mapDispatchToProps = bindActionCreators<ActionCreatorsMapObject<IDispatchProps>>({
  loadItem,
  // will raise an error if some binds are missing
}, dispatch);

@Jessidhia
Copy link

Jessidhia commented Dec 20, 2017

Your workaround hopefully doesn't work with --strictFunctionTypes 😄

I haven't yet used this in practice, as almost all thunk actions I am dispatching are functionally void, but I wonder if a helper like this would work well:

/**
 * Wrapper used when calling thunk actions that were bound by react-redux's connect.
 *
 * Usage:
 *
 * ```ts
 * const result = thunkResult(this.props.thunkAction(args))
 * ```
 *
 * TypeScript doesn't support transforming the signature of functions,
 * so it's not possible to make a generic type that would correctly
 * accept the thunk functions while changing their return type. This has
 * to be done with a roundtrip through any.
 *
 * This is only correct if the action actually was dispatched. By default,
 * this will check whether the result is or is not a function to ensure it
 * is being used correctly, but if the result really must be a function, the
 * second argument can be set to true to disable the check.
 */
export function thunkResult<R> (
  thunkAction: ThunkAction<R, any, any>,
  resultIsFunction?: true
): R {
  const result = thunkAction as any as R
  if (typeof result === 'function' && !resultIsFunction) {
    throw new Error('thunkResult mistakenly used on undispatched thunk')
  }
  return result
}

@superjose
Copy link

If anyone is coming here because they just wanted to make a simple import to the store, and import is not working... Here's the solution:

import { applyMiddleware, createStore } from 'redux';
import * as thunk from 'redux-thunk';
import * as promise from 'redux-promise-middleware';
import { rootReducer } from './root-reducer';
const middleware = applyMiddleware(promise.default(), thunk.default);
export default createStore(rootReducer, middleware);

We had to call "default" after each import.

Here's the post in Medium

@atrauzzi
Copy link

atrauzzi commented Feb 13, 2018

@superjose - You may want to look into allowSyntheticDefaultImports instead as what you're doing is a bit of a kludge.

Also worth noting that the behaviour can be impacted by your bundler (webpack, jspm, etc...).

@superjose
Copy link

superjose commented Feb 13, 2018

@atrauzzi Thank you very much for pointing that out. I honestly don't know if it's a problem with my tsconfig or create-react-app typescript. But it is certainly not picking that up:

image

The very first line has allowSyntheticDefaultImports

Edit:
Wait, wait. I think I found the problem. I'm currently under a workspace that has 2 tsconfig.json. One is for the client and the other is for an express backend (each one in their own folder structure). Somehow the client version was picking up the server version and it was causing issues. I've enabled allowSyntheticDefaultImports on both and that seemed to solve the issue.

Thank you!

@paweljurczynski
Copy link

In my case upgrading redux-thunk was enough:

redux-thunk": "^2.3.0"

@rpmonteiro
Copy link

For the love of god, I've been battling with this error for hours:
image

Argument of type 'ThunkAction<Promise<any>, RootState, null, AnyAction>' is not assignable to parameter of type 'Action'. Property 'type' is missing in type 'ThunkAction<Promise<any>, RootState, null, AnyAction>'.

And here's the thunk:
image

const verifyUserEmail = (token: string): ThunkAction<Promise<any>, RootState, ApiClient, AnyAction> => {
  return async (dispatch, getState, api) => {
    dispatch(Actions.verifyUserEmailRequest());

    try {
      await api.users.verifyUserEmail(token);
      return dispatch(Actions.verifyUserEmailSuccess());
    } catch (e) {
      captureException(e);
      if (e.code === 404) {
        dispatch(Actions.verifyUserEmailFailure('wrong_token'));
      } else if (e.code === 400) {
        dispatch(Actions.verifyUserEmailFailure('already_verified'));
      }
      return dispatch(Actions.verifyUserEmailFailure('some_other_error')); // TODO: types for errors?
    }
  };
};

I've tried all sorts of combinations.. already on the 4th page of google. So you know.

@Redux 4.0
@redux-thunk 2.3.0

Any ideas, guys?
@dupski

@superjose
Copy link

superjose commented Jun 8, 2018

@rpmonteiro

export function verifyUserEmailThunk(token: string) {
  return (dispatch: Dispatch<RootState>, getState: () => RootState, api) => {
    return new Promise<string>(async (resolve, reject) => {
      // I don't know where Actions is coming from
      dispatch(Actions.verifyUserEmailRequest());

      try {
        await api.users.verifyUserEmail(token);
        return dispatch(Actions.verifyUserEmailSuccess());
      } catch (e) {
        // I don't know where captureException is coming from.
        captureException(e);
        if (e.code === 404) {
          dispatch(Actions.verifyUserEmailFailure('wrong_token'));
        } else if (e.code === 400) {
          dispatch(Actions.verifyUserEmailFailure('already_verified'));
        }
        return dispatch(Actions.verifyUserEmailFailure('some_other_error')); // TODO: types for errors?
      }
      const result = await firebase.signIn(email, password);

      // We have to query the server to see if the user has admin rights, or not.
      if (result.succeeded) { return; }
      dispatch(loginActions.loginFailed(result.error!.message));
      
    });
  };
}

// Then you consume it like this:

import { verifyUserEmailThunk} from '@common/redux/actions/emailThunk';
import store from '@src/root-redux/store';
export class MyComponent extends React.Component<MyProps, MyState> {
  handleVerifyEmail() {
     store.dispatch(verifyUserEmailThunk(this.state.token));
  }
}

That's how I got it working! (I honestly didn't know how to quickly rewrite it to the const verifyUserEmail form.) I'm using TypeScript as well!

I don't know if that is going to work! If api doesn't work. What I do, is that I just import the "API" object from an external file to the file that the thunk is currently in. (If that is not clear, then let me know and I'll try to better explain myself).

@rpmonteiro
Copy link

rpmonteiro commented Jun 8, 2018 via email

@superjose
Copy link

@rpmonteiro: exactly!! Now I don't remember where I got the info from. I think it was from a create-react-app-typescript project that was lying around. It was also very painful to get it right. Let me know if that solves the issue!

@shishir-srivastava
Copy link

shishir-srivastava commented Jun 9, 2018

I spent most of yesterday battling with this issue, and managed to solve it like so:

/*
    AppAction - extends Redux::Action
    AppState - my app's state shape
*/
export type Thunk = ThunkAction<void, AppState, null, AppAction>;
export type Dispatch<S> = ThunkDispatch<S, null, AppAction>;

In my connected components, instead of using Dispatch from redux, I use the above definition.

const mapDispatchToProps = (
	dispatch: Dispatch<AppState>
): ContentOverlayDispatchProps => ({
	dismissOverlay: () => dispatch(hideContentOverlay()),
	loadContent: (key: string) => dispatch(loadDocument(key))
});

In this case, hideContentOverlay returns an AppAction object, and loadDocument is a Thunk.

I'm using:
redux: 3.7.2
redux-thunk: 2.3.0
react-redux: 5.0.7
react: 16.2.0
typescript: 2.6.2
types/react-redux: 5.0.20

Hope this helps! :)

@rpmonteiro
Copy link

rpmonteiro commented Jun 9, 2018 via email

@mehmetnyarar
Copy link

@shishir-srivastava What you suggest works with an async function with no parameters but it doesn't if I pass any parameters to an async func. How did you manage?

I'm wondering what I'm missing..

export type RTAction = ThunkAction<void, RootState, null, RootAction>;
export type RTDispatch = ThunkDispatch<RootState, null, RootAction>;
export type GetState = () => RootState;

This works:

export const someActionAsync: RTAction = () => async (
  dispatch: RTDispatch,
  getState: GetState
) => {
  try {
    const url = getUrl();
    const res = await axios.get(url);
    dispatch(someAction(res.data));
  } catch (error) {
    // console.log("actionAsync.error", error);
  }
};

This doesn't:

export const anotherActionWithParamAsync: RTAction = (param: string) => async (
  dispatch: RTDispatch,
  getState: GetState
) => {
  try {
    const url = getUrl(param);
    const res = await axios.get(url);
    dispatch(anotherActionWithParam(res.data));
  } catch (error) {
    // console.log("anotherActionWithParamAsync.error", error);
  }
};

redux: 4.0.0
redux-thunk: 2.3.0
react-redux: 5.0.7
react: 16.3.1
typescript: 2.9.2
types/react-redux: 6.0.2

@iamjem
Copy link

iamjem commented Jun 27, 2018

This seems to work for me:

// file: actions/users.ts
import { ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';

export const updateEmail: ActionCreator<
  ThunkAction<Promise<UpdateEmailAction>, State, null, UpdateEmailAction>
> = (req: IUpdateEmailRequest) => {
    return (dispatch, getState) => {
      let action: UpdateEmailAction = {
        type: CHANGE_EMAIL,
        state: States.INFLIGHT
      };
      dispatch(action);

      return ajax('/api/change-email', {
        method: 'POST',
        body: JSON.stringify(req)
      })
      .then(async (resp) => {
        if (resp.ok) {
          let action: UpdateEmailAction = {
            type: CHANGE_EMAIL,
            state: States.SYNCED
          };
          dispatch(action);
          return action;
        }

        let action: UpdateEmailAction = {
          type: CHANGE_EMAIL,
          state: States.ERRORED
        };
        dispatch(action);
        return action;
      })
      .catch(() => {
        let action: UpdateEmailAction = {
          type: CHANGE_EMAIL,
          state: States.ERRORED
        };
        dispatch(action);
        return action;
      });
    };
}

I omitted some specifics of my implementation, but this captures the gist of it. I generally return a promise from my async actions, and that promise always resolves to the action that I dispatched. This generally helps me remove some boilerplate state management from components. You could of course use async for your ThunkAction like some of the other examples show here, or not return anything at all (e.g. ThunkAction<void, State, null, UpdateEmailAction>

@zefj
Copy link

zefj commented Jan 28, 2019

For future reference and anyone struggling with complaints about the action creator argument to store.dispatch, make sure your thunk middleware is properly defined, along with type assertion. Just wasted three hours trying to get it to work.

This is the error I had on store.dispatch(someActionCreator()); line:

Argument of type 'ThunkAction<Promise<void>, IState, undefined, Actions>' is not assignable to parameter of type 'Actions'.

And then some variable complaints, depending on which part of the code I blindly tweaked. Turns out the middleware typing was wrong. This is the correct one (all the other entity types follow the advice in this thread):

const store = createStore(
    rootReducer,
    applyMiddleware(
        thunk as ThunkMiddleware<IState, Actions>, // this is crucial
    ),
);

@dacre-denny
Copy link

dacre-denny commented Feb 19, 2019

This approach worked for me:

/* Imports from react, redux, and redux-thunk */
import { ThunkDispatch } from "redux-thunk";
import { connect } from 'react-redux';
import { Dispatch } from "react";

interface State {
    something: string;
}

/* Thunk action */
export const myThunkAction= (arg: number) => {
    return (dispatch: Dispatch<State>, getState: () => State) => {
        console.log('calling myThunkAction!');
    }
}

/* Connecting a component, plugging everything together */
export default connect(
    (state: State) => state,
    (dispatch: ThunkDispatch<State, undefined, RootAction>) => ({
        myThunkAction: (argument : number) => dispatch(myThunkAction(argument))
    }))( MyComponent )

Dependencies:
"react": "^16.7.0",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"react-redux": "^6.0.0",
"@types/react-redux": "^7.0.1",
"@types/react": "^16.7.21",

@stavalfi
Copy link

If anyone still here because you can't configure the dispatch to accept thunks:

You can configure the generic params of applyMiddleware:

import { logger } from 'redux-logger'
import { applyMiddleware, createStore, combineReducers } from 'redux'
import thunk, { ThunkDispatch } from 'redux-thunk'

const middleware = applyMiddleware<ThunkDispatch<YourState, ThunkThirdParam, Actions>, YourState>(thunk, logger,...)

No use of as here... @zefj

@rafaelbiten
Copy link

I was struggling with this too and passing types to createStore solved the problem for me:

const store = createStore<StoreState, Actions, any, any>(appReducer, initialState, enhancers)

note: I just tried and we can also pass types to thunk as ThunkMiddleware<IState, Actions>

@wolfuser99
Copy link

I have it working as:

import {ThunkDispatch} from 'redux-thunk';
import {Action} from '@reduxjs/toolkit';
import {useDispatch} from 'react-redux';

import {store} from './configureStore';

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = ThunkDispatch<RootState, undefined, Action<string>>; 

export const useAppDispatch = () => useDispatch<AppDispatch>();

@MartinGrue
Copy link

I dont know if this is still relevant but i solved:
Argument of type 'ThunkAction<Promise<void>, IState, undefined, Actions>' is not assignable to parameter of type 'Actions'.
aka "i cannot dispatch thunkActions from the store"

store.dispatch(INSERT_YOUR_THUNK_ACTION_HERE()) // error

by typing the createStore return value:

export type appStore = Store<usersState, usersActions>
 & { dispatch: ThunkDispatch<usersState, undefined, usersActions>;} // this the good stuff
; 
export const configureStore = (usersInitialState: usersState): appStore => {
  const appstore = createStore(
    usersReducer,
    usersInitialState,
    applyMiddleware(thunk)
  );
  return appstore;
};

then you can even drop the type assertion in createStore:

const store = createStore(
    rootReducer,
    applyMiddleware(
        thunk as ThunkMiddleware<IState, Actions>, // this is not needed anymore
    ),
);

@mwalkerr
Copy link

For anyone else having this issue while using redux-toolkit, it could be due to using the spread operator for the middleware. Eg. I started with:

export const createStore = () => {
    return configureStore({
        reducer: appSlice.reducer,
        middleware: [...getDefaultMiddleware(), loggerMiddleware],
        preloadedState: appState
    })
}

and updated to

export const createStore = () => {
    return configureStore({
        reducer: appSlice.reducer,
        middleware: getDefaultMiddleware => getDefaultMiddleware().concat(loggerMiddleware),
        preloadedState: appState
    })
}

https://redux-toolkit.js.org/api/configureStore

Alternately, you may pass a callback function that will receive getDefaultMiddleware as its argument, and should return a middleware array. This lets you skip importing getDefaultMiddleware separately. If using TypeScript, prefer using this syntax, as we provide a more strongly-typed version of getDefaultMiddleware that will correctly retain the types of the provided middleware when constructing the store.

https://redux-toolkit.js.org/api/getDefaultMiddleware

It is preferrable to use the chainable .concat(...) and .prepend(...) methods of the returned MiddlewareArray instead of the array spread operator, as the latter can lose valuable type information under some circumstances.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests