Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Correct TypeScript typing example for Redux Thunk actions
import {Action, ActionCreator, Dispatch} from 'redux';
import {ThunkAction} from 'redux-thunk';
// Redux action
const reduxAction: ActionCreator<Action> = (text: string) => {
return {
type: SET_TEXT,
text
};
};
// Redux-Thunk action
const thunkAction: ActionCreator<ThunkAction<Action, IState, void>> = (
text: string
) => {
return (dispatch: Dispatch<IState>): Action => {
return dispatch({
type: SET_TEXT,
text
});
};
};
// Async Redux-Thunk action
const asyncThinkAction: ActionCreator<
ThunkAction<Promise<Action>, IState, void>
> = () => {
return async (dispatch: Dispatch<IState>): Promise<Action> => {
try {
const text = await Api.call();
return dispatch({
type: SET_TEXT,
text
});
} catch (e) {}
};
};
@dderg
Copy link

dderg commented Feb 10, 2018

asyncThinkAction -> asyncThunkAction

@lassemon
Copy link

lassemon commented Feb 11, 2018

Cannot find name 'IState'.

@danielericlee
Copy link

danielericlee commented Feb 13, 2018

@milankorsos This is awesome! This is the clearest example for typing Thunk actions I've come across...

By any chance, do you have examples of your approach for typing a connected component that consumes Thunk actions via mapDispatchToProps? I'm struggling to properly type the parameters to connect...

@bradjohns0n
Copy link

bradjohns0n commented Apr 23, 2018

@danielericlee @milankorsos I'd like to know this as well

@ljuba95
Copy link

ljuba95 commented Apr 26, 2018

@danielericlee @milankorsos @erkrapide I also can't find anywhere fully typed connected stateful component dispatching thunk actions. Help! :)

@activebiz
Copy link

activebiz commented May 28, 2018

Great example. Please post one for mapDispatchToProps and connect.

@Paul-Pushkarov
Copy link

Paul-Pushkarov commented Jun 3, 2018

@lassemon
It's just your State's type

@activebiz

Great example. Please post one for mapDispatchToProps and connect.

+1

@jballantinecondev
Copy link

jballantinecondev commented Jun 26, 2018

Thanks for the example. I'm also struggling to figure out if it's possible to correctly type a component that dispatches thunk actions, so an example there would be really helpful too.

@mpcen
Copy link

mpcen commented Jul 4, 2018

@jballantinecondev have you ever found anything on correctly typing a component that dispatches thunk actions?

@medington
Copy link

medington commented Jul 25, 2018

This appears to be out of date with current Redux v4 and @types/redux-thunk@2.3 typings. In particular, the Dispatch type no longer takes a "State" generic parameter.

redux 3.7.2 typings:

export interface Dispatch<S> {
    <A extends Action>(action: A): A;
}

redux 4.0 typings:

export interface Dispatch<A extends Action = AnyAction> {
  <T extends A>(action: T): T;
}

@lazarljubenovic
Copy link

lazarljubenovic commented Jul 27, 2018

How do you use the action which returns a thunk later? I cannot feed it into dispatch function in mapDispatchToProps, since the action creator returns a function (a thunk), and not an action (it doesn't have type property on it).

const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => {
  return {
    handleAsyncAction: () => {
      dispatch(Actions.HomePage.asyncFetch())
    },
  }
}
(140,16): Argument of type '(dispatch: Dispatch<AnyAction>) => Promise<void>' is not assignable to parameter of type 'AnyAction'.
  Property 'type' is missing in type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.

I can make it work at runtime by casting to any, but ew.

@lmcarreiro
Copy link

lmcarreiro commented Jul 31, 2018

@lazarljubenovic same problem here...

@milankorsos how did you resolve this case?

@lmcarreiro
Copy link

lmcarreiro commented Jul 31, 2018

@lazarljubenovic after some time, I found a solution... here is what I'm doing:

import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { MyState } from '../my-store/state';
​​
// ...

const mapDispatchToProps = (dispatch: ThunkDispatch<MyState, void, Action>) => {
  return {
    onRequestClick: (arg: any) => dispatch(myAsyncAction(arg)),
  };
}function myAsyncAction(arg: any) {
  return async (dispatch: ThunkDispatch<MyState, void, Action>) => {

    dispatch(requestDataStartAction(arg));let result: string;

    try {
      result = await fetch('/url', { data: arg });
    }
    catch (error) {
      dispatch(requestDataErrorAction(arg, error));
      return error;
    }
    
    dispatch(requestDataSuccessAction(arg, result));
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent)

@jfbloom22
Copy link

jfbloom22 commented Sep 25, 2018

I ran across this trying to find an example of using redux-thunk with typescript. The only thing missing from the examples above is how to type getState.
I opened an issue on redux-thunk, but did not receive much help: reduxjs/redux-thunk#213 (comment)

Also, in my use case, I am dispatching things before and after the promise resolves.

@tswaters
Copy link

tswaters commented Sep 26, 2018

The response from timdorr pointed at the tests, which are quite helpful actually. From those tests, I picked up a pattern that works pretty well. In my main reducers file (where I pull together types for all of the actions and state slices), I'll export a few types for use elsewhere in the application:

export type ThunkResult<R> = ThunkAction<R, IStoreState, null, IStoreActions>

export type ThunkDispatch = ThunkDispatch<IStoreState, null, IStoreActions>

export default combineReducers<IStoreState, IStoreActions>({
  // various reducers
})

When defining an async thunk action, they'll use ThunkResult<R> as the return type, this looks like this:

export function someAction1(): ThunkResult<void> {
  return (dispatch, getState) => {
    // do da dispatch
    // also `getState` is typed as `() => IStoreState`
  }
}

This also works with async functions (and by nature, promise.then works):

export function asyncAction2(): ThunkResult<Promise<boolean>> {
  return async (dispatch, getState) => {
    await someThing()
    return true
  }
}

export function asyncAction3(): ThunkResult<void> {
  return (dispatch, getState) => {
    dispatch(asyncAction2()).then(result => assert(result))  // no type errors!
  }
}

When using mapDispatchToProps, I'll use ThunkDispatch.... this only seems necessary when getting dispatch through react-redux -- if I have a reference to the store, the dispatch action seems to come with the additional typings for handling dispatches with thunks fine.... pulling it all together for a connected container --

const selector= createSelector((state: IStoreState) => state.someSlice, slice => ({slice}))

const mapDispatchToProps = (dispatch: ThunkDispatch): ComponentActionProps => ({
  handleDrawCard: () => dispatch(asyncAction()) // works fine, even with no {type: string} on asyncAction
})

const mapStateToProps = (state: IStoreState): ComponentConnectedProps => selector(state)

export const Container = connect(mapStateToProps, mapDispatchToProps)(ContainerComponent)

@peacefullatom
Copy link

peacefullatom commented Sep 28, 2018

Hey, @tswaters!

I'm also trying to tie-up Typescript with React/Redux/Thunk/etc 😃

For those who are just like me, one should mention that at first, you need to import all the stuff:

import { ThunkAction, ThunkDispatch } from 'redux-thunk';

Thank you! 👍

@aaronlifton
Copy link

aaronlifton commented Sep 29, 2018

these types might be useful to people using react 4 & redux thunk 2.3.

for typing thunks

ActionCreator<ThunkAction<Promise<any>,IState,null,AnyAction>>

where Promise<any> is an example of return type of the thunk
and AnyAction is the type of action you'll dispatch

and for typing dispatch

ThunkDispatch<IState, null, AnyAction>

similar arguments, see source of redux-thunk

@nhagen
Copy link

nhagen commented Oct 13, 2018

This works when I pass mapDispatchToProps as a function.

impression IProps {
thunkAction: (arg: IArg) => Promise<IArg>
}
class Comp extends React.Component<IProp> { ... }

const mapStateToProps = ...
const mapDispatchToProps = (dispatch: ThunkDispatch<IState, any, IAction>) => ({
  thunkAction: (arg: Partial<IArg>) =>
    dispatch(thunkAction(arg))
});
connect(mapStateToProps, mapDispatchToProps)(Comp);

Doesn't work when I pass it as an object

//  Type 'ThunkAction<Promise<ThunkAction>, IState, any, IAction<any>>' is not assignable to type 'Promise<IArg>'.
connect(mapStateToProps, { thunkAction })(Comp);

Whats missing here?

@seansean11
Copy link

seansean11 commented Nov 11, 2018

As @aaronlifton2 mentioned up above it looks like the type signature for ThunkAction has changed in newer versions of redux-thunk.

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

Here's an example of how I set up my Thunk types: https://gist.github.com/seansean11/196c436988c1fdf4b22cde308c492fe5

@glowkeeper
Copy link

glowkeeper commented Nov 27, 2018

@tswaters

export type ThunkResult<R> = ThunkAction<R, IStoreState, null, IStoreActions>

Thank you!

@piazzatron
Copy link

piazzatron commented Jun 6, 2019

This works when I pass mapDispatchToProps as a function.

impression IProps {
thunkAction: (arg: IArg) => Promise<IArg>
}
class Comp extends React.Component<IProp> { ... }

const mapStateToProps = ...
const mapDispatchToProps = (dispatch: ThunkDispatch<IState, any, IAction>) => ({
  thunkAction: (arg: Partial<IArg>) =>
    dispatch(thunkAction(arg))
});
connect(mapStateToProps, mapDispatchToProps)(Comp);

Doesn't work when I pass it as an object

//  Type 'ThunkAction<Promise<ThunkAction>, IState, any, IAction<any>>' is not assignable to type 'Promise<IArg>'.
connect(mapStateToProps, { thunkAction })(Comp);

Whats missing here?

Has this been solved yet? I get a similar error when trying to use the object form of mapDispatch; using the function form works fine.

Unless I'm missing something obvious, this is a rather sharp edge :(

Following the introductory Redux docs, you are guided to prefer the object form of mapDispatch, and then in the typescript section, you are introduced the ThunkDispatch type, which doesn't work with the object form of mapDispatch.

@bensoutendijk
Copy link

bensoutendijk commented Jul 27, 2019

I am migrating to Typescript for fun, but I am not understanding how I can avoid all the boilerplate when I am writing my actions and reducers like this:

localUserReducer.js

const initialState = {
  fetching: false,
  fetched: false,
  user: undefined,
  errors: undefined,
};

export default function (state = initialState, action) {
  switch (action.type) {
    case 'GET_USER_PENDING':
      return {
        ...state,
        fetching: true,
      };
    case 'GET_USER_FULFILLED':
      return {
        ...state,
        fetching: false,
        fetched: true,
        user: action.payload,
      };
    case 'GET_USER_REJECTED':
      return {
        ...state,
        fetching: false,
        errors: action.payload,
      };
    default:
      return state;
  }
}

localUserActions.js

import axios from 'axios';

export const getUser = () => async (dispatch) => {
  dispatch({ type: 'GET_USER_PENDING' });
  try {
    const { data } = await axios.get('/api/auth/local/current');
    dispatch({ type: 'GET_USER_FULFILLED', payload: data });
  } catch (err) {
    dispatch({ type: 'GET_USER_REJECTED', payload: err.response.data });
  }
};

Would I be expected to write an interface for each _PENDING, _FULFILLED, and _REJECTED action?

I may have a huge misunderstand of redux-thunk as I am a newbie. I don't understand how I can send _REJECTED actions if I use the implementation of Typescript and redux-thunk documented here: https://redux.js.org/recipes/usage-with-typescript#usage-with-redux-thunk

@ar2zee
Copy link

ar2zee commented Nov 20, 2019

export type ThunkResult<R> = ThunkAction<R, IStoreState, null, IStoreActions>

Just want to say thank you, I struggled with it and your answer helped me solve the problem. 😄

@alexseitsinger
Copy link

alexseitsinger commented Dec 24, 2019

Thanks @milankorsos!

@LoginovSO
Copy link

LoginovSO commented Dec 19, 2020

Please hellp me )))

export type ThunkResult2<R> = ThunkAction<R, AppStateType, null, Action>
export type ThunkDispatch2 = ThunkDispatch<AppStateType, null, Action>
export const login = (auth: any): ThunkResult2<Promise<{a: boolean, b: string}>> => async (dispatch: ThunkDispatch2) => {
    const res =  await AuthApi.authentication(auth);
    const r = dispatch(AuthActions.login(new Auth({ ...res })));
    return {
        a: true,
        b: '123'
    } // this very well work
}

// but
......

            onSubmit={ async (values) => {
                const res = await dispatch(login(values))
                console.log(res.a) // this problem
            }}

TS2339: Property 'a' does not exist on type 'ThunkAction { a: boolean; b: string; }>, CombinedState{ auth: never; }>, null, Action >'

@shehi
Copy link

shehi commented Mar 10, 2021

I still can't locate IStoreState

@Goran7777
Copy link

Goran7777 commented Aug 9, 2021

Maybe this is right place to use "any" when i must make type for thunk action in my props.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment