microsoft / redux-dynamic-modules

Modularize Redux by dynamically loading reducers and middlewares.
https://redux-dynamic-modules.js.org
MIT License
1.07k stars 116 forks source link

Update generic IModule interface #113

Open kazamov opened 4 years ago

kazamov commented 4 years ago

Please update IModule interface, so it will be possible to set Action type, e.g.

export interface IModule<State, Action = AnyAction> { /**

codeocelot commented 4 years ago

Question: what shape are your actions such that they aren't compatible with redux's AnyAction interface? The AnyAction interface only requires a type field and lets any other assigned field be any.

kazamov commented 4 years ago

I am using 'flux-standard-action' library typings: FSAAuto and ErrorFSAAuto. Link to typings file https://github.com/redux-utilities/flux-standard-action/blob/master/src/index.d.ts.

I have a reducer export function appReducer(state: AppState = initialAppState, action: AppActions): AppState {...}

Where AppActions is export type AppActions = FSAAuto<AppActionTypes.REQUEST_APP_METADATA, AppMetadataPayload> | FSAAuto<AppActionTypes.REQUEST_APP_METADATA_SUCCEED, App> | ErrorFSAAuto<AppActionTypes.REQUEST_APP_METADATA_FAILED>

In my module export const AppModule: ISagaModule<AppAwareState> = { id: 'appModule', reducerMap: { appModule: appReducer, }, // TODO: fix typing issue sagas: [rootSaga], };

I see a TS error:

(property) IModule.reducerMap?: ReducersMapObject<AppAwareState, AnyAction> | undefined Reducers for the module

Type '{ appModule: (state: AppState | undefined, action: AppActions) => AppState; }' is not assignable to type 'ReducersMapObject<AppAwareState, AnyAction>'. Types of property 'appModule' are incompatible. Type '(state: AppState | undefined, action: AppActions) => AppState' is not assignable to type 'Reducer<AppState, AnyAction>'. Types of parameters 'action' and 'action' are incompatible. Type 'AnyAction' is not assignable to type 'AppActions'. Type 'AnyAction' is not assignable to type 'ErrorFluxStandardActionWithPayload<AppActionTypes.REQUEST_APP_METADATA_FAILED, Error, undefined>'. Property 'error' is missing in type 'AnyAction' but required in type 'ErrorFluxStandardAction<AppActionTypes.REQUEST_APP_METADATA_FAILED, Error, undefined>'.ts(2322) index.d.ts(47, 3): 'error' is declared here. Contracts.d.ts(13, 5): The expected type comes from property 'reducerMap' which is declared here on type 'ISagaModule'

Thus it would be nice to have possibility to set own action types. By default it can be set to AnyAction

codeocelot commented 4 years ago

@kazamov did you figure this out? If you have a repo where I can see the problem, that may better help me understand your problem.

bloomdido commented 4 years ago

I am encountering the same problem. I am trying to use connected-react-router and getting the following error:

TypeScript error in /myapp/src/app/store/module.ts(13,3): Type '{ router: Reducer<RouterState, LocationChangeAction>; }' is not assignable to type 'ReducersMapObject<AppModuleState, AnyAction>'. Types of property 'router' are incompatible. Type 'Reducer<RouterState, LocationChangeAction>' is not assignable to type 'Reducer<RouterState, AnyAction>'. Types of parameters 'action' and 'action' are incompatible. Type 'AnyAction' is not assignable to type 'LocationChangeAction'. TS2322

Here is my code:

import {ISagaModule} from 'redux-dynamic-modules-saga'
import {connectRouter, routerMiddleware, RouterState} from 'connected-react-router'
import history from 'utils/history'

const routerReducer = connectRouter(history)

interface AppModuleState {
  router: RouterState,
}

const AppModule: ISagaModule<AppModuleState> = {
  id: 'app',
  reducerMap: {
    router: routerReducer,
  },
  middlewares: [
    routerMiddleware(history),
  ],
  sagas: [],
}

export default AppModule

I believe this might be because of strict contravariance and the way that RouterState is defined by connected-react-router. I was able to workaround it by adding the following assertion:

import {Reducer} from 'redux'
.
.
.
const routerReducer = connectRouter(history) as Reducer<RouterState>

I don't know if the best way to handle this is to change the signature of IModule to accept a generic action type, or to just assign manually as I've done above. Just thought this might be helpful to anyone else who encounters this issue.

kazamov commented 4 years ago

Hi @bloomdido.

I have found a workaround for this issue. If cast the type of "reducerMap" property to ReducersMapObject, everything is fine. E.g.

export const RoleModule: ISagaModule<RoleAwareState> = {
  id: 'roleModule',
  reducerMap: {
    roleModule: roleReducer,
  } as ReducersMapObject<RoleAwareState>,
  sagas: [rootSaga],
};
bloomdido commented 4 years ago

@kazamov Yeah, I think both of our solutions are about getting around Typescript's strict contravariance for callbacks.

From what I understand, the issue is that any action can always be an AnyAction, but an AnyAction cannot always be any action.

forgo commented 4 years ago

From what I understand, the issue is that any action can always be an AnyAction, but an AnyAction cannot always be any action.

@bloomdido , This seems to be the same issue I am now encountering with putting a ThunkAction in the initialActions part of the module. Any suggestions on resolving this? I have not been able to find a way to eliminate this problem.

export const fetchToplineData = (): ThunkAction<any, any, any, AnyAction> => {
    return (dispatch, getState) => {
        const mockData = [{ a: 1, b: 2, c: 3, s: getState() }];
        dispatch(toplineDataAvailable(mockData));
    };
};
export function getToplineModule(): IModule<any> {
    return {
        // Unique id of the module
        id: "topline",
        // Maps the Store key to the reducer
        reducerMap: {
            topline: toplineReducer,
        },
        initialActions: [fetchToplineData()], // this line errors
        finalActions: [],
    };
}

Error Produced TS2741: Property 'type' is missing in type 'ThunkAction<any, any, any, AnyAction>' but required in type 'AnyAction'.

kazamov commented 4 years ago

From what I understand, the issue is that any action can always be an AnyAction, but an AnyAction cannot always be any action.

@bloomdido , This seems to be the same issue I am now encountering with putting a ThunkAction in the initialActions part of the module. Any suggestions on resolving this? I have not been able to find a way to eliminate this problem.

export const fetchToplineData = (): ThunkAction<any, any, any, AnyAction> => {
    return (dispatch, getState) => {
        const mockData = [{ a: 1, b: 2, c: 3, s: getState() }];
        dispatch(toplineDataAvailable(mockData));
    };
};
export function getToplineModule(): IModule<any> {
    return {
        // Unique id of the module
        id: "topline",
        // Maps the Store key to the reducer
        reducerMap: {
            topline: toplineReducer,
        },
        initialActions: [fetchToplineData()], // this line errors
        finalActions: [],
    };
}

Error Produced TS2741: Property 'type' is missing in type 'ThunkAction<any, any, any, AnyAction>' but required in type 'AnyAction'.

Hi @forgo, I described the workaround above, you should cast 'reducerMap' to proper type

forgo commented 4 years ago

Hi @kazamov , just to be clear, my error is on initialActions not the reducerMap. Not sure if that is related. I had already tried your example above casting reducerMap as ReducersMapObject<any> and ReducersMapObject<any, any> and still have this error before posting here.