Skip to content

Instantly share code, notes, and snippets.

@milankorsos
Last active September 26, 2024 00:51
Show Gist options
  • Save milankorsos/ffb9d32755db0304545f92b11f0e4beb to your computer and use it in GitHub Desktop.
Save milankorsos/ffb9d32755db0304545f92b11f0e4beb to your computer and use it in GitHub Desktop.
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) {}
};
};
@aaronlifton
Copy link

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

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

@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

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

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