Coder Social home page Coder Social logo

Comments (46)

Methuselah96 avatar Methuselah96 commented on June 11, 2024 48

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

from redux-thunk.

vanzinvestor avatar vanzinvestor commented on June 11, 2024 21
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);

from redux-thunk.

flocbit avatar flocbit commented on June 11, 2024 6

@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
    }
}

from redux-thunk.

Eversonv4 avatar Eversonv4 commented on June 11, 2024 5

@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!

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024 4

@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).)

from redux-thunk.

sevgeek avatar sevgeek commented on June 11, 2024 4

@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! 👍🏻

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024 3

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024 1

@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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024 1

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024 1

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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.

from redux-thunk.

mendesbarreto avatar mendesbarreto commented on June 11, 2024

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.

from redux-thunk.

yamachu avatar yamachu commented on June 11, 2024

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

reduxjs/react-redux#1911

maybe related
#247

from redux-thunk.

allicanseenow avatar allicanseenow commented on June 11, 2024

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

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

@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?

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

I'll repeat my usual refrain :)

REPROS! WE NEED REPROS!

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

@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.

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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.

from redux-thunk.

allicanseenow avatar allicanseenow commented on June 11, 2024

@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.

from redux-thunk.

allicanseenow avatar allicanseenow commented on June 11, 2024

@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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

@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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

@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.

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

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.

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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.

from redux-thunk.

Methuselah96 avatar Methuselah96 commented on June 11, 2024

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.

from redux-thunk.

blumk avatar blumk commented on June 11, 2024

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",

from redux-thunk.

sevgeek avatar sevgeek commented on June 11, 2024

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

from redux-thunk.

sevgeek avatar sevgeek commented on June 11, 2024

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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

from redux-thunk.

sevgeek avatar sevgeek commented on June 11, 2024

@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

from redux-thunk.

allicanseenow avatar allicanseenow commented on June 11, 2024

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"],
}

from redux-thunk.

adriencorbin avatar adriencorbin commented on June 11, 2024

Many Thanks, this workaround works for me too: import 'redux-thunk/extend-redux'

from redux-thunk.

bokusunny avatar bokusunny commented on June 11, 2024

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! :)

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

@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.

from redux-thunk.

pailhead avatar pailhead commented on June 11, 2024

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.

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

@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)

from redux-thunk.

pailhead avatar pailhead commented on June 11, 2024

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 :(

from redux-thunk.

pailhead avatar pailhead commented on June 11, 2024

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

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

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

from redux-thunk.

pailhead avatar pailhead commented on June 11, 2024

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.

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

@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.

from redux-thunk.

pailhead avatar pailhead commented on June 11, 2024
(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)

from redux-thunk.

markerikson avatar markerikson commented on June 11, 2024

@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.

from redux-thunk.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.