feat(store): add createReducer function#1746
Conversation
|
Preview docs changes for 3f2befd at https://previews.ngrx.io/pr1746-3f2befd/ |
|
The creation of reducers is very nice, and as you mentioned, having to add more explicit type checking on each function is unfortunate. Both that and having to wrap the reducers for each feature feels like a step back in ease of use upfront even if you gain a less verbose way of creating a reducer function. |
|
Preview docs changes for dd3d865 at https://previews.ngrx.io/pr1746-dd3d865/ |
|
@alex-okrushko is this initial set of work ready to merge? |
This one uses |
We can land with the token now, and follow-up with the fn if you prefer. |
|
Preview docs changes for 5704f62 at https://previews.ngrx.io/pr1746-5704f62/ |
|
FYI,
|
|
Preview docs changes for 0bb7409 at https://previews.ngrx.io/pr1746-0bb7409/ |
|
My vote goes to using |
|
How would we feel about having the initial state as first argument? It would allow to skip the array, but perhaps it's useful to have the distinction between the two? export const reducer = createReducer<State>(
initialState,
on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user })),
on(AuthActions.logout, () => initialState)
);Implementation: export function createReducer<S>(
initialState: S,
...ons: { reducer: On<S>; types: string[] }[]
): Reducer<S> {
const map = new Map<string, On<S>>();
for (let on of ons) {
for (let type of on.types) {
map.set(type, on.reducer);
}
}
return function(state: S = initialState, action: Action): S {
const reducer = map.get(action.type);
return reducer ? reducer(state, action) : state;
};
} |
I thought about that as well, but the |
|
As an additional note, you won't be able to use export function reducer(state, action) {
return createReducer(...);
}
StoreModule.forFeature('feat', reducer) |
We want to treat Actions like Events... In DOM, events listeners use 'on' prefix, so that's another ➕for
Will look into that as well. |
|
@alex-okrushko sounds like |
|
LGTM other than Tim's comments |
|
Preview docs changes for c3816d4 at https://previews.ngrx.io/pr1746-c3816d4/ |
|
Preview docs changes for be5f4d8 at https://previews.ngrx.io/pr1746-be5f4d8/ |
|
Should be ready to go 😀 |
|
@timdeschryver @brandonroberts
I was trying to remove the need to provide the generic for the I'd like to change it to the following now: export const reducer = createReducer(
initialState,
on(AuthApiActions.loginSuccess, (state, { user }) => ({ ...state, user })),
on(AuthActions.logout, () => initialState)
); |
I believe it would be beneficial if this was also included in the docs as an AoT comment. |
|
Will you leave a comment about this in #1762? |
|
Will do :) |
|
Hi @alex-okrushko ! Would mind giving a little background on why this change was made? When would you suggest using the injection token approach over the standard one? Thanks! |

PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Introduces
createReducerfunction, that has the same functionality asreducerfromts-actions(created by @cartant).It helps remove Action unions and dramatically reduce the code needed for reducers. Compare:
However, there are a few caveats that are caused by microsoft/TypeScript#3755 and microsoft/TypeScript#7547 (comment) with the latter acknowledging current TS limitation.
Sometimes
reducerfunctions inon(...)have to be explicitly typed with their return type. Let's look at the example:If the
Stateinon(LayoutActions.closeSidenav, (): Stateis not provided it would narrow the type to{showSidenav: false}and{showSidenav: true}correspondingly, which would not match the interface ofState(which is{showSidenav: boolean}).TS highlights this as error, so providing the
Stateis requiredSometimes typos could be missed
Let's look at another example (reduced for brevity):
Note the
ending: false, that missedp. The original intention was to spread the state and override withpending: false, however because of the typopendingwon't be overridden and instead extraendingproperty will be added.This will not match the
Stateinterface, butStatecould be extended from this interface with type (because entirestateof typeStateis spread).switch-based reducer function catches these types of errors, because providing the return type of the
reducerfunction is a norm:function reducer(state: State = initialState, action: Action): State.Solution: This unfortunate error can be also caught by explicitly typing the
reducerwithinon:on(AuthApiActions.loginFailure, (state, { error }): State => ({..})),For me, that's an acceptable solution. Maybe we should recommend to always type it. I wish the
createSelector<State>would've caught it, but it's not currently possible due to TS limitations mentioned above.Note: if the
Statetyped could not be extracted from the returned object literal, the error would be displayed property even without explicit return type, e.g.:Closes # #1724
Does this PR introduce a breaking change?
Other information