diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 87ba8f4121..fa63fde732 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -194,21 +194,17 @@ export type SliceActionCreator

= PayloadActionCreator

; // @public export type SliceCaseReducers = { - [K: string]: CaseReducer> | CaseReducerWithPrepare>; + [K: string]: CaseReducer> | CaseReducerWithPrepare>; }; export { ThunkAction } // @public export type ValidateSliceCaseReducers> = ACR & { - [P in keyof ACR]: ACR[P] extends { - reducer(s: S, action?: { - payload: infer O; - }): any; + [T in keyof ACR]: ACR[T] extends { + reducer(s: S, action?: infer A): any; } ? { - prepare(...a: never[]): { - payload: O; - }; + prepare(...a: never[]): Omit; } : {}; }; diff --git a/src/createSlice.ts b/src/createSlice.ts index 6dfeefcdb4..58275d5033 100644 --- a/src/createSlice.ts +++ b/src/createSlice.ts @@ -12,6 +12,7 @@ import { ActionReducerMapBuilder, executeReducerBuilderCallback } from './mapBuilders' +import { Omit } from './tsHelpers' /** * An action creator atttached to a slice. @@ -110,7 +111,7 @@ export type CaseReducerWithPrepare = { export type SliceCaseReducers = { [K: string]: | CaseReducer> - | CaseReducerWithPrepare> + | CaseReducerWithPrepare> } /** @@ -187,11 +188,11 @@ export type ValidateSliceCaseReducers< ACR extends SliceCaseReducers > = ACR & { - [P in keyof ACR]: ACR[P] extends { - reducer(s: S, action?: { payload: infer O }): any + [T in keyof ACR]: ACR[T] extends { + reducer(s: S, action?: infer A): any } ? { - prepare(...a: never[]): { payload: O } + prepare(...a: never[]): Omit } : {} } diff --git a/src/tsHelpers.ts b/src/tsHelpers.ts index bbde02b85b..d5dcda749f 100644 --- a/src/tsHelpers.ts +++ b/src/tsHelpers.ts @@ -87,3 +87,5 @@ type UnionToIntersection = (U extends any : never) extends ((k: infer I) => void) ? I : never + +export type Omit = Pick> diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts index 7a1d7bb484..65f8008bc3 100644 --- a/type-tests/files/createSlice.typetest.ts +++ b/type-tests/files/createSlice.typetest.ts @@ -70,7 +70,7 @@ function expectType(t: T) { ? payload.reduce((acc, val) => acc * val, state) : state * payload, addTwo: { - reducer: (s, { payload }) => s + payload, + reducer: (s, { payload }: PayloadAction) => s + payload, prepare: (a: number, b: number) => ({ payload: a + b }) @@ -175,6 +175,65 @@ function expectType(t: T) { expectType(counter.actions.concatMetaStrLen('test').meta) } +/** + * Test: access meta and error from reducer + */ +{ + const counter = createSlice({ + name: 'test', + initialState: { counter: 0, concat: '' }, + reducers: { + // case: meta and error not used in reducer + testDefaultMetaAndError: { + reducer(_, action: PayloadAction) {}, + prepare: (payload: number) => ({ + payload, + meta: 'meta' as 'meta', + error: 'error' as 'error' + }) + }, + // case: meta and error marked as "unknown" in reducer + testUnknownMetaAndError: { + reducer(_, action: PayloadAction) {}, + prepare: (payload: number) => ({ + payload, + meta: 'meta' as 'meta', + error: 'error' as 'error' + }) + }, + // case: meta and error are typed in the reducer as returned by prepare + testMetaAndError: { + reducer(_, action: PayloadAction) {}, + prepare: (payload: number) => ({ + payload, + meta: 'meta' as 'meta', + error: 'error' as 'error' + }) + }, + // case: meta is typed differently in the reducer than returned from prepare + testErroneousMeta: { + reducer(_, action: PayloadAction) {}, + // typings:expect-error + prepare: (payload: number) => ({ + payload, + meta: 1, + error: 'error' as 'error' + }) + }, + // case: error is typed differently in the reducer than returned from prepare + testErroneousError: { + reducer(_, action: PayloadAction) {}, + // typings:expect-error + prepare: (payload: number) => ({ + payload, + meta: 'meta' as 'meta', + error: 1 + }) + } + } + }) +} + /* * Test: returned case reducer has the correct type */