[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report
[ ] Feature request
[ ] Documentation issue or request
This code from the example is causing a typescript error not assignable
export const reducers: ActionReducerMap<State> = {
layout: fromLayout.reducer,
router: fromRouter.routerReducer
};
ActionReducerMap is causing a typescript error when strictFunctionTypes is true
turning strictFunctionTypes to false, fixes the errors.
Should compile without errors
node: 9.5.0
ngrx: 6.0.0.beta.0
use any type for the second type variable. this is impossible to fix now until angular support ts2.8
can I keep this open until support for 2.8?
What is the workaround? Can you post the code sample? I am not sure what "use any type for the second type variable" is actually referring to
FYI
export const reducers: ActionReducerMap<State, any /* put any here */> = {
layout: fromLayout.reducer,
router: fromRouter.routerReducer
};
@brandonroberts TS 2.8 is supported by the angular 6.1 beta. Do we have to wait until angular-7 and ngrx-7 for this fix or will there be a ngrx-6.1?
Angular 6.1 with support for Typescript 2.9 has now been released. is it a good time to revisit this issue?
Not 100% sure if the issue I'm having is the same, but my problem is as follows:
type LayoutAction = BreadcrumbAction |
DeviceAction |
SidenavAction |
HeaderAction |
MainAction
export const layoutReducers: ActionReducerMap<LayoutState, LayoutAction> = {
breadcrumb: breadcrumbReducer,
device: deviceReducer,
sidenav: sidenavReducer,
header: headerReducer,
main: mainReducer,
}
In the example above, it won't compile because each of the reducers have their own set of actions that are placed in a corresponding union type (BreadcrumbAction, DeviceAction, etc.). Each reducer expects to receive an action of its corresponding type. e.g. the breadcrumbReducer has following signature:
export function breadcrumbReducer(
state = initialBreadcrumbState,
action: breadcrumbActions.BreadcrumbAction,
): BreadcrumbState
Now, I can't use the union of all the different Action types, LayoutAction in ActionReducerMap<LayoutState, LayoutAction> because LayoutAction is not something I can pass to all the reducers which expect only a specific piece of the LayoutAction union.
What should I be doing in this case? any seems like the only choice I have so far.
@MrSnappingTurtle yes the case you're describing is indeed what this issue is for.
Using any is the solution for now.
We have been rolling out --strictFnTypes to all google internal TypeScript users. The solution we have come to use (while not looking-great, but pretty simple to implement) is all reducers should be written as:
import {Action} from '@ngrx/store';
function someReducer(state = defaultState, action: Action) {
const specificAction = action as SomeSpecificAction;
<previous body of the reducer>
}
I find your solution actually mind blowing @rkirov because it's so simple.
I was taking a stab to change the ActionReducerMap types but couldn't come up with a solution, at this point while I'm not an expert I feel like this is unsolvable for now...
Hey guys,
We're running into the same issue after generating a 'feature' from schematics and trying to add multiple reducers into the same feature state.
I think it has to do with the fact that Actions are grouped together in an enum:
export enum UserActionTypes {
LoadUsers = '[Users] Load Users'
}
where our combined reducer is defined as:
export const reducer: ActionReducerMap<UsersState> = {
users: usersReducer
};
This produces the following error with "strictFunctionTypes":
error TS2322: Type '{ users: (state: UsersState | undefined, action: LoadUsers) => UsersState; }' is not assignable to type 'ActionReducerMap<UsersState, Action>'.
Types of property 'users' are incompatible.
Type '(state: UsersState | undefined, action: LoadUsers) => UsersState' is not assignable to type 'ActionReducer<UsersState, Action>'.
Types of parameters 'action' and 'action' are incompatible.
Type 'Action' is not assignable to type 'LoadUsers'.
Types of property 'type' are incompatible.
Type 'string' is not assignable to type 'UsersActionTypes'.
9 export const reducer: ActionReducerMap<UsersState> = {
~~~~~~~
If we change the enum from the first code block above to an object instead, the problem goes away:
export const UserActionTypes = {
LoadUsers: '[Users] Load Users'
};
Is this the correct solution? If so, can this be fixed in the schematics and documentation?
@gregjacobs there is a huge drawback to defining your action types like this. The compiler will no longer be able to validate the type field of your actions afterwards, as now it'll be defined as string instead of your actual set of actions (i.e. "action1" | "action2" | ...).
You would be much better of using the any hack discussed above or to go with the suggestion of @rkirov.
@ohjames I don't think that's true actually. The reason this issue comes up in the first place is because the Action class exported by ngrx defines the type property as a string. ~So extending it to create your own XyzAction classes already casts your enum to a string.~ Update: Nvm that last part, I don't think that is true either. I guess the real issue is that Action has the type property defined as a string and you're right, it's the default second generic param of ActionReducerMap
Unless Action takes a generic for the type property? I'm not at my desk right now but I'll take a look. If that's the case, then I would def agree that the enum is useful.
As a side note, I was wondering why the discriminated union never really worked in my reducers. Having type defined as a string would explain it
~@ohjames err, unless you're explicitly re-casting it in your XyzAction classes?~
// nvm this
//export class XyzAction implements //Action {
// readonly type: MyActions = //MyActions.LoadThings;
//}
I'll look into fixing this issue, it may well be possible with TypeScript 3.1 but I also fear that we may need partial generic inference to do it. That was scheduled for TypeScript 3.2 but something changed and it is now scheduled for a future release.
@gregjacobs I also forgot to mention it but yeah you noticed it, without the discriminated union on the type property (i.e. if it's defined as string) the transpiler can no longer do type inference based on the type prop. I'd actually advise you use "action creators" to create your actions rather than this enum business, it leads to much more concise and easy to read code. I highly recommend the libraries "typesafe-actions" and "unionize" which can both do this for you. They are both very different also so check which one fits your use case. Action creators rock! The react/redux community have been using them for years and the angular community should benefit from it also!!
@ohjames Thanks for the suggestions! Yes it would be great if this can be fixed. And I'd also love to reduce the amount of boilerplate with actions :) Will look into those two libraries - thx!
I have just run into this issue while upgrading from 5 to 7. So...
Let's see the definitions for ActionReducerMap and ActionReducer:
export declare type ActionReducerMap<T, V extends Action = Action> = {
[p in keyof T]: ActionReducer<T[p], V>;
};
and
export interface ActionReducer<T, V extends Action = Action> {
(state: T | undefined, action: V): T;
}
According to the documentation, your _fromLayout.reducer_ function could return a LayoutState type. Then you pass it to the _reducers_ constants. But if you do so, the types LayoutState and ActionReducer
in _layout.reducer.ts:_
export const reducer: ActionReducer<LayoutState, Action> = (state, action) =>
reducerFunction(state, action as fromLayout.LayoutActions);
function reducerFunction(state = initialState, action: fromLayout.LayoutActions): LayoutState {
...
}
And now you can do this in index.ts:
export const reducers: ActionReducerMap<State> = { layout: fromLayout.reducer};
I got a similar situation, and after a lot of time, checking my code, I found a missing property return in the switch case actions in the reducer.
Make sure you load your previous state with spread operator {...state, property: action.payload} and then set what you want. In my case on of my actions was missing, no error there, but got the exact same error as you.
Hope it helps!
I got pretty the same issue as @gregjacobs. I solved it by changing the type from ActionReducerMap<State> to ActionReducerMap<any, Action>. This was not clear from any of the official examples.
Re-open this if its still an issue with the latest version
Good call @papiliond ... that was a huge pain... I can't imaging most people would be able to even figure this out with this help
This is still an issue in latest.
@papiliond's workaround is excellent though.
Just wanted to mention that @rkirov's method is just as good. Thanks to both @rkirov and @papiliond
@brandonroberts I'm not sure if the original poster is still active to reactivate this, but it is still an issue (workarounds notwithstanding). I could report it as a new issue, but probably it is better if this one is reopened.
wholy moly.. why do I have to specify 2nd arg as any .. fixed for me..but its weird
Most helpful comment
FYI