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

ThunkAction is not assignable to parameter of type 'AnyAction'. #333

Closed
mendesbarreto opened this issue Apr 13, 2022 · 46 comments
Closed

ThunkAction is not assignable to parameter of type 'AnyAction'. #333

mendesbarreto opened this issue Apr 13, 2022 · 46 comments

Comments

@mendesbarreto
Copy link

mendesbarreto commented Apr 13, 2022

Bug Report

Package name / version

[email protected]

Description

I am trying to use a simple method from my store:

store.dispatch(ThunkAction);

It is working on run time, but I am receiving an error message on Typescript:

TS2345: Argument of type 'ThunkAction<Promise<AuthorizationDto | null>, State, unknown, SetAuthorizationAction>' is not assignable to parameter of type 'AnyAction'.   Property 'type' is missing in type 'ThunkAction<Promise<AuthorizationDto | null>, State, unknown, SetAuthorizationAction>' but required in type 'AnyAction'. 

I already tried to import the extend-redux like that:

import 'redux-thunk/extend-redux';

But the problem is the run time does not find file =S.

Screen Shot 2022-04-13 at 3 36 16 PM

I already tried every single solution that's I found on the previous issues, but non of them worked for me.

Steps to reproduce

  • Install the redux-thunk using "npm install"
  • Create a ThunkAction
  • Call the store.dispatch(ThunkAction)
  • See typescript Type error

Expected behavior

No error message from typescript when using store.dispatch(ThunkAction)

Environment

  • OS: macOS Monterey 12.3.1
  • Node/npm version: node v16.14.2 / npm 8.5
  • Platform: React Native iOS/Android
@mendesbarreto mendesbarreto changed the title ThunkActio is not assignable to parameter of type 'AnyAction'. ThunkAction is not assignable to parameter of type 'AnyAction'. Apr 13, 2022
@markerikson
Copy link
Contributor

Do you actually have the store configured to use the thunk middleware?

It would really help to see a CodeSandbox or a repo that shows this problem happening.

@mendesbarreto
Copy link
Author

mendesbarreto commented Apr 20, 2022

I will create a project. But is really simple to reproduce, you just install the [email protected] in a typescript project and try to use store.dispatch(ThunkAction); from a redux store.

@markerikson
Copy link
Contributor

@mendesbarreto unfortunately I don't know what the ThunkAction variable looks like in this example, or how the middleware was set up, or what TS version you're using, or how TS itself is configured. So, I really need to see an actual project if at all possible, so I can investigate it myself.

@yamachu
Copy link

yamachu commented Apr 20, 2022

@mendesbarreto
Are you by any chance using react-redux v8?
I have created the following Issue.

reduxjs/react-redux#1911

maybe related
#247

@Methuselah96
Copy link
Member

Methuselah96 commented Apr 23, 2022

For me import 'redux-thunk/extend-redux'; was causing a Webpack error:

ERROR in ./source/scripts/app.tsx 5:0-34
Module not found: Error: Can't resolve 'redux-thunk/extend-redux' in 'C:\Users\nbier\Documents\test-app'

I ended up using an empty type-import to clue Webpack into the fact I wasn't trying to import a JS module:

import type {} from 'redux-thunk/extend-redux';

@allicanseenow
Copy link

allicanseenow commented Apr 26, 2022

Got the same issue, only for react-redux v8. Would love to have a fix for this.

@Methuselah96
Copy link
Member

Methuselah96 commented Apr 26, 2022

@allicanseenow

react-redux v8 was where I was running into the issue as well. Are you importing redux-thunk/extend-redux at all? Did you try import type mentioned in my previous comment? What's the exact error message you're getting?

@markerikson
Copy link
Contributor

I'll repeat my usual refrain :)

REPROS! WE NEED REPROS!

We really need actual projects that show the error happening so I can investigate.

@Methuselah96
Copy link
Member

Methuselah96 commented Apr 26, 2022

FWIW, I believe the reason this is coming up as an issue with react-redux v8 is because the v7 useDispatch() was typed as:

// NOTE: the first overload below and note above can be removed if redux-thunk typings add an overload for
// the Dispatch function (see also this PR: https://github.com/reduxjs/redux-thunk/pull/247)
export function useDispatch<TDispatch = Dispatch<any>>(): TDispatch;
export function useDispatch<A extends Action = AnyAction>(): Dispatch<A>;

The first function overload means you can dispatch literally anything (as a workaround for redux-thunk).

But in v8 it's now typed as:

export declare const useDispatch: <AppDispatch extends Dispatch<AnyAction> = Dispatch<AnyAction>>() => AppDispatch;

which only allows for Action objects.

So this issue isn't "new" it's just been masked by the loose types in the old react-redux type definitions.


That's just background for why this is coming up now. If you're hitting this error now as a user you should either:

  • Use Redux Toolkit and define typed hooks (which will be correctly typed and allow thunk actions)
  • If you're using vanilla Redux, most bundlers won't like import 'redux-thunk/extend-redux' in a regular TypeScript file because they think you're trying to import JavaScript so you can either:
    • Use import type {} from 'redux-thunk/extend-redux'
    • Put /// <reference types="redux-thunk/extend-redux" /> in a .d.ts file that's included in your type-checking
    • Put import 'redux-thunk/extend-redux' in a .d.ts file that's included in your type-checking

@markerikson
Copy link
Contributor

Huh, good eye! I suppose I accidentally "fixed" this while doing some cleanup on the types :)

@Methuselah96
Copy link
Member

@markerikson Here's your repro. :)

The line I linked to errors with Webpack because it's trying to import 'redux-thunk/extend-redux' as a JS module.

@markerikson
Copy link
Contributor

Heh, thanks :)

So backing up, it sounds like there's two different "issues" going on here:

  • ThunkAction not dispatching correctly, which we think is due to a change in the v8 useDispatch types when the user wasn't actually following our TS setup guidelines
  • The separate question of "what is the right way to import the extended global types". Given that it's a pure types-only file, I think it's safe to assume that it has to be done as import type and I just flat-out was wrong with my import instructions.

Is there anything else going on atm beyond those? Seems like we could consider this resolved if not.

@allicanseenow
Copy link

allicanseenow commented Apr 26, 2022

@markerikson Thanks for looking into this. I am at work but I'll see if I can reproduce the bug locally later. The bug occurred to me in a project where I don't use the toolkit but the old redux action/reducer pattern instead.

@allicanseenow
Copy link

@Methuselah96 I was just importing directly import { ThunkAction } from 'redux-thunk'. I'll test out the other way to import later as I have recently reverted the react-redux version back to v7.

@Methuselah96
Copy link
Member

@allicanseenow Yeah, an import from 'redux-thunk' won't extend the Redux types to allow dispatching thunk actions, you have to import 'redux-thunk/extend-redux' somewhere.

@Methuselah96
Copy link
Member

Methuselah96 commented Apr 26, 2022

@markerikson Yeah, that sounds right.

Although on your first point, for vanilla Redux users the TypeScript guide does not mention the need to import redux-thunk/extend-redux. Without that import the most obvious way of typing a useDispatch hook with Dispatch<ThunkAction> will not work. But Redux Toolkit should work fine.

@markerikson
Copy link
Contributor

In theory, our advice of type AppDispatch = typeof store.dispatch ought to work even with vanilla createStore + applyMiddleware, but it's been so long since I've tried it that I'm not 100% sure.

@Methuselah96
Copy link
Member

Yeah if the middleware is the only enhancer it might work, but I don't think composing multiple enhancers with the Redux types works right now without reduxjs/redux#3776 (can we get that merged :)). I haven't tested it out either, so maybe it would work.

@Methuselah96
Copy link
Member

Methuselah96 commented Apr 26, 2022

Can confirm that type AppDispatch = typeof store.dispatch does not work with vanilla createStore + applyMiddlware. Here's the CodeSandbox (not a working app, just checking the types). I didn't get an error in the TypeScript playground, but that might be because we haven't removed @types/react-redux. I don't think the PR mentioned above would fix this scenario, but I haven't dug into why this doesn't work.

@markerikson
Copy link
Contributor

Huh, interesting. We do have a bunch of custom type-level code in configureStore to ensure that dispatch ends up with the correct types based on the supplied middleware. I would have thought that applyMiddleware would at least do some of that behavior.

@Methuselah96
Copy link
Member

It should, but I don't think people rely on createStore doing that in practice since it's been at least partially broken for a long time.

@blumk
Copy link

blumk commented Jun 1, 2022

I'm using Redux Toolkit and upgrading the package version to at least 1.7.0 solved this issue for me.

"@reduxjs/toolkit": "1.7.2",

@flocbit
Copy link

flocbit commented Jun 15, 2022

@Methuselah96's solution pointed me in the right direction but since redux-thunk/src/types.ts currently has a typescript error which caused my pipelines to fail, I created a redux-thunk.d.ts file:

// This is required to fix redux thunk errors introduced with react-redux version 8
import 'redux'

declare module 'redux' {
    /**
     * Overload for bindActionCreators redux function, returns expects responses
     * from thunk actions
     */
    function bindActionCreators<
        ActionCreators extends ActionCreatorsMapObject<any>
    >(
        actionCreators: ActionCreators,
        dispatch: Dispatch
    ): {
        [ActionCreatorName in keyof ActionCreators]: ReturnType<
            ActionCreators[ActionCreatorName]
        > extends ThunkAction<any, any, any, any>
            ? (
                  ...args: Parameters<ActionCreators[ActionCreatorName]>
              ) => ReturnType<ReturnType<ActionCreators[ActionCreatorName]>>
            : ActionCreators[ActionCreatorName]
    }

    /*
     * Overload to add thunk support to Redux's dispatch() function.
     * Useful for react-redux or any other library which could use this type.
     */
    export interface Dispatch<A extends Action = AnyAction> {
        <ReturnType = any, State = any, ExtraThunkArg = any>(
            thunkAction: ThunkAction<ReturnType, State, ExtraThunkArg, A>
        ): ReturnType
    }
}

@sevgeek
Copy link

sevgeek commented Jun 20, 2022

Today I ran into the same problem when I wanted to dispatch initiate action for my API endpoint from redux-toolkit/rtk-query library.

image

@reduxjs/toolkit: 1.8.2
react-redux: 8.0.2

@Methuselah96
Copy link
Member

How are you dispatching it? If you're using useDispatch make sure you're using a typed hook.

@sevgeek
Copy link

sevgeek commented Jun 20, 2022

How are you dispatching it? If you're using useDispatch make sure you're using a typed hook.

@Methuselah96 Yes, I am using typed useAppDispatch hook in custom hook:

export type { RootStore, AppDispatch };

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootStore> = useSelector;

export default store;
function useDispatchSafeDealAction() {
  const dispatch = useAppDispatch();

  const dispatchAction = React.useCallback(() => {
    dispatch(OAuthAPI.endpoints.getOAuthToken.initiate());
  }, [dispatch]);

  return [dispatchSafeDealAction];
}

Typescript error:

image

@markerikson
Copy link
Contributor

@sevgeek : can you show your actual store setup code, preferably as a CodeSandbox or Github repo?

@sevgeek
Copy link

sevgeek commented Jun 21, 2022

@sevgeek : can you show your actual store setup code, preferably as a CodeSandbox or Github repo?

@markerikson Yes, sure. I use for configure store very simple reducer, my API slice reducer and additional listener middleware.
https://codesandbox.io/s/react-redux-toolkit-wbzkgc?file=/src/store/index.ts

I think I found a script to reproduce the error.

When I disable the connection of my [APISlice.reducerPath], type AppDispatch gets the correct types and allows me to work with ThunkDispatch.

image

When I return connection my [APISlice.reducerPath], AppDispatch get a different type and then I get type error in my custom hook: https://codesandbox.io/s/react-redux-toolkit-wbzkgc?file=/src/hooks/index.tsx

image
image

@markerikson
Copy link
Contributor

@sevgeek : I think the issue here is the use of .prepend([SomeMiddleware]), and more specifically, the use of an array as the argument.

If I uncomment that API slice reducer, and then remove the square brackets from both the .prepend and .concat lines, the AppDispatch type looks to be correct.

We do some complex types manipulation to figure out how middleware might alter the type of store.dispatch, and it looks like that just doesn't work right if you pass in an array as an argument. (In the case where you wanted to add multiple middleware at once, you pass them in as separate args, like .concat(middleware1, middleware2).)

@sevgeek
Copy link

sevgeek commented Jun 21, 2022

@markerikson I transferred the pluggable middlewares from .prepend([]) to .concat(middleware1, middleware2, ...) or .concat([middleware1, middleware2, ...]) and the type error disappeared.
Thank you very much for your help! 👍🏻

jomaora added a commit to CoorpAcademy/components that referenced this issue Jul 21, 2022
StefanoA1 added a commit to CoorpAcademy/components that referenced this issue Jul 22, 2022
* Typing actions with ThunkDispatach

* Adding ts fix recommended by reduxjs/redux-thunk#333

* rename type and clear double declare global for Window

Co-authored-by: stefano <[email protected]>
@allicanseenow
Copy link

Forgot to comment. I was able to resolve the issue globally in my personal project by importing 'redux-thunk/extend-redux', as suggested by @Methuselah96

I created a custom file custom.d.ts that has

import 'redux-thunk/extend-redux'

And I include it in tsconfig.json:

{
   ...,
   "include" : ["custom.d.ts"],
}

@adriencorbin

This comment was marked as duplicate.

@vanzinvestor
Copy link

vanzinvestor commented Oct 21, 2022

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { AnyAction, combineReducers } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
 
const rootReducers = combineReducers();

type AppState = ReturnType<typeof rootReducers>;

type TypedDispatch<T> = ThunkDispatch<T, any, AnyAction>;
 
export const useAppDispatch = () => useDispatch<TypedDispatch<AppState>>();

export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;

// USE
const dispatch = useAppDispatch();

const state = useAppSelector((state: AppState) => state.xxx);

@Eversonv4
Copy link

@Methuselah96's solution pointed me in the right direction but since redux-thunk/src/types.ts currently has a typescript error which caused my pipelines to fail, I created a redux-thunk.d.ts file:

// This is required to fix redux thunk errors introduced with react-redux version 8
import 'redux'

declare module 'redux' {
    /**
     * Overload for bindActionCreators redux function, returns expects responses
     * from thunk actions
     */
    function bindActionCreators<
        ActionCreators extends ActionCreatorsMapObject<any>
    >(
        actionCreators: ActionCreators,
        dispatch: Dispatch
    ): {
        [ActionCreatorName in keyof ActionCreators]: ReturnType<
            ActionCreators[ActionCreatorName]
        > extends ThunkAction<any, any, any, any>
            ? (
                  ...args: Parameters<ActionCreators[ActionCreatorName]>
              ) => ReturnType<ReturnType<ActionCreators[ActionCreatorName]>>
            : ActionCreators[ActionCreatorName]
    }

    /*
     * Overload to add thunk support to Redux's dispatch() function.
     * Useful for react-redux or any other library which could use this type.
     */
    export interface Dispatch<A extends Action = AnyAction> {
        <ReturnType = any, State = any, ExtraThunkArg = any>(
            thunkAction: ThunkAction<ReturnType, State, ExtraThunkArg, A>
        ): ReturnType
    }
}

it worked just fine! thank you very much!

@bokusunny
Copy link

Hi, I could do store.dispatch(ThunkAction) by type casting thunkMiddleWare before passing to applyMiddleware like applyMiddleware(thunkMiddleWare as ThunkMiddleware<State, Actions>). (in case you created store with deprecated createStore util).
See: https://codesandbox.io/s/redux-thunk-ts-forked-lzl9d2?file=/src/index.ts

The type of store is:

// without type casting
const store: Store<State, Action<any>> & {
    dispatch: unknown;
}
store.dispatch(testGetState()); // complained like `Argument of type 'ThunkResult<void>' is not assignable to parameter of type 'Action<any>'.ts(2345)`... :(

// with type casting
const store: Store<State, Action<any>> & {
    dispatch: ThunkDispatch<State, undefined, Actions>;
}
store.dispatch(testGetState()); // working! :)

@markerikson
Copy link
Contributor

@bokusunny : you really shouldn't ever use the TS Store type, and definitely not to cast the store setup.

The right answer here is to use Redux Toolkit's configureStore, which A) is the recommended way to create a Redux store in the first place, and B) has much better type inference than createStore does.

@pailhead
Copy link

pailhead commented Feb 3, 2023

How can i make my own magical dispatch that injects stuff:

export const useAppDispatch = (): AppDispatch => {
  const { modulePath } = useModuleContext()
  const dispatch = useDispatch()
  const moduleDispatch = useMemo(() => {
    const stableMeta = { modulePath }
    return (action: Parameters<typeof dispatch>[0]) => {
      action.meta = stableMeta
      dispatch(action)
    }
  }, [dispatch, modulePath])
  return moduleDispatch
}

It's complaining it can't assign ThunkAction<...> to AnyAction.

@markerikson
Copy link
Contributor

@pailhead : a few different issues with that:

  • You should follow our TS guidelines for inferring the type of dispatch from the store setup, and then define pre-typed hooks that have that type baked in: https://redux.js.org/tutorials/typescript-quick-start#define-root-state-and-dispatch-types
  • You should use that type here: const dispatch = useAppDispatch():
  • Instead of (action: Parameters<typeof dispatch>[0]), you should type that callback function with the type of AppDispatch instead
  • You should make sure to include return, like return dispatch(action)

@pailhead
Copy link

pailhead commented Feb 3, 2023

I don't want to use prepare which seems to be the only way to type the meta, and i want to use MyAction<T=void> = PayloadAction<T> & {meta: MyMeta}, hence the idea to make this kind of a dispatch to complement that :(

@markerikson
Copy link
Contributor

@pailhead I'm not asking about "why" you're trying to do this - I'm telling you how to fix it :)

@pailhead
Copy link

pailhead commented Feb 3, 2023

Type 'ThunkAction<...> is not assignable to type 'AnyAction :(

@markerikson
Copy link
Contributor

@pailhead I know. Please follow the instructions I gave you to fix that TS error!

@pailhead
Copy link

pailhead commented Feb 3, 2023

I'm going around in circles typing as AppDispatch seems to leave my action as any. typeof dispatch is the same as AppDispatch so other than returning the last dispatch i don't understand what your instructions are saying that i haven't already done. (Other than the thing where i make action:any). Thanks anyway ❤️

export const useAppDispatch = (): AppDispatch => {
  const { modulePath } = useModuleContext()
  const dispatch : AppDispatch = useDispatch()
  const moduleDispatch = useMemo(() => {
    const stableMeta = { modulePath }
    const foo:: AppDispatch  = (action: any) => {
      action.meta = stableMeta
      return dispatch(action)
    }
  }, [dispatch, modulePath])
  return moduleDispatch
}

Ie. this works, but action:any.

@markerikson
Copy link
Contributor

markerikson commented Feb 3, 2023

@pailhead :

Assuming you're using Redux Toolkit and following our store setup that I linked,, AppDispatch should roughly be something along the lines of ThunkDispatch & Dispatch<AnyAction> (going from memory).

Then, you need to use that type for this new "override dispatch" function you're returning:

  const moduleDispatch = useMemo(() => {
    const stableMeta = { modulePath }
    const mySpecialDispatch: AppDispatch = (action) => {
      action.meta = stableMeta
      return dispatch(action)
    }
  }, [dispatch, modulePath])

and definitely do not include action: any!

So:

  • Make sure that you have your store setup correct and that hovering over the inferred AppDispatch type shows that it includes ThunkDispatch. If this type is wrong, then nothing else will be typed right.
  • Use that type as shown above.

@pailhead
Copy link

pailhead commented Feb 3, 2023

(alias) type WarpDispatch = ThunkDispatch<{
    layout: LayoutState;
}, undefined, AnyAction> & Dispatch<AnyAction>

image
Hm won't let me upload a screenshot, but basically this doesn't type action in const mySpecialDispatch:AppDispatch = (action)... (i get any)

@markerikson
Copy link
Contributor

@pailhead : well, it should :)

Honestly, using this issue thread as a chat support system doesn't work well.

If you're still having issues, please come by the #redux channel in the Reactiflux Discord ( https://www.reactiflux.com ), and have a CodeSandbox or a Github repo that shows this happening.

The types and approach I told you will work, but it's also possible that there's something odd about your specific project setup and configuration that we don't know about.

@reduxjs reduxjs locked as resolved and limited conversation to collaborators Feb 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests