reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.87k stars 15.27k forks source link

Can't dispatch another action inside action with typescript #4051

Closed ali-ajam closed 3 years ago

ali-ajam commented 3 years ago

This is what redux has for action and type is mandatory. Are we trying to prevent dispatching action in another action? if not then this is a bug because when I need to dispatch another action I don't have a type or payload is just calling another action.

`/**

My code:

export const getDiagnostics = () => async (
  dispatch: Dispatch<VehicleAction | (dispatch: Dispatch<VehicleAction>) => Promise<void>>
): Promise<void> => {
  return axios
    .post(
      process.env.REACT_APP_ENDPOINT,
      {
        diagnosticsRequest: {
        },
      },
      {
        headers: {
          Authorization: 'Bearer ' + store.getState().auth.token,
        },
      }
    )
    .then((response) => {
      if (response.data.status === 'inProgress') {
        dispatch(
          getRequest(
            response.data.url,
            SET_DIAGNOSTICS_STATUS,
            SET_DIAGNOSTICS
          )
        );
      }
    })
    .catch((error) => {
      dispatch({
        type: SET_DIAGNOSTICS_STATUS,
        payload: 'failure',
      });
    });
};

TypeScript Error

Type 'VehicleAction | ((dispatch: Dispatch<InVehicleAction>) => Promise<void>)' does not satisfy the constraint 'Action<any>'.
  Property 'type' is missing in type '(dispatch: Dispatch<VehicleAction>) => Promise<void>' but required in type 'Action<any>'.ts(2344)
index.d.ts(21, 3): 'type' is declared here.

Environment Details

"@types/node": "^12.20.4",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-error-boundary": "^3.1.1",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.3.0"
markerikson commented 3 years ago

I'd need to see where you're trying to do dispatch(getDiagnostics()), but this looks like you are running into an issue that we've specifically documented in our "Usage with TS" guide page. The basic store Dispatch TS type does not know anything about thunks, so TS refuses to let you dispatch a thunk. You have to use an updated form of Dispatch that understands thunks are an acceptable thing to dispatch, and we show how to do that here:

Also, based on your comment it sounds like you might not yet be comfortable with the distinction between an "action" and a "thunk". I'd suggest reading through the "Redux Fundamentals" tutorial in our docs, and specifically these sections:

antoscarface commented 3 years ago

I have the same exact issue (as many users as I see googling). I did exactly how the doc suggests, but it seems it doesn't solve.

This is my configure store:

import { configureStore } from "@reduxjs/toolkit";
// import { ThunkAction } from "redux-thunk";
import rootReducer, { RootState } from "./rootReducer";

const store = configureStore({
  reducer: rootReducer,
});

export type AppDispatch = typeof store.dispatch;

export default store;

Then my root reducer, with RootState type and thunk type:

import { AnyAction, combineReducers } from "@reduxjs/toolkit";
import todos from "features/todoList/todoSlice";
import visibilityFilter from "features/visibilityFilter/visibilityFilterSlice";
import { ThunkAction } from "redux-thunk";

const rootReducer = combineReducers({
  todos,
  visibilityFilter
});

export type RootState = ReturnType<typeof rootReducer>;
export type AppThunkAction<R> = ThunkAction<R, RootState, unknown, AnyAction>;
export default rootReducer;

Finally, the thunk action defined:

import {AppThunkAction} from "app/rootReducer";

export const addTodo = (text: string): AppThunkAction<Promise<void>> => async (
  dispatch
) => {
  const newTodo: Todo = {
    id: Math.random()
      .toString(36)
      .substr(2, 9),
    completed: false,
    text: text
  };
  dispatch(todoSlice.actions.addTodo(newTodo));
};

Here the error is: image

And this is how the dispatch type is inferred: image

What am I doing wrong?

markerikson commented 3 years ago

Hmm. So I I think part of the issue here is that the type of dispatch within the thunk hasn't been associated with the AppDispatch type. In other words, at that point in the code, we still think it's a plain ol' Dispatch. But, if you look at the type of ThunkAction in the redux-thunk typings, I actually don't see a way to modify that via generics:

export type ThunkAction<
  TReturnType,
  TState,
  TExtraThunkArg,
  TBasicAction extends Action
> = (
  dispatch: ThunkDispatch<TState, TExtraThunkArg, TBasicAction>,
  getState: () => TState,
  extraArgument: TExtraThunkArg,
) => TReturnType;

@phryneas , any ideas here?

phryneas commented 3 years ago

@antoscarface @markerikson from what I see this has nothing to do with the initial topic of this issue (dispatching a thunk from within a thunk), but is the "redux 4.0.5 and 4.1.0 installed next to each other cause AppDispatch to become Dispatch<AnyAction>" thing.

=> please check that your node_modules folder does not somewhere contain a redux version 4.0.5

antoscarface commented 3 years ago

You saved me the life!!

I don't understand exactly the part "redux 4.0.5 and 4.1.0 installed next to each other", is it possible to install two different versions of redux?

My case I think is that I was using @reduxjs/toolkit@1.5.0 and I have also redux@4.0.5. I removed redux and it works finally!!

Thank you very much!

dimaqq commented 3 years ago

A better solution is probably to use the resolutions clause in package.json, @antoscarface