diff --git a/src/configureStore.ts b/src/configureStore.ts index b2ae29c262..bcc7a92cba 100644 --- a/src/configureStore.ts +++ b/src/configureStore.ts @@ -129,7 +129,7 @@ export interface EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends Middlewares = [ThunkMiddlewareFor] + M extends Middlewares = [ThunkMiddlewareFor] >(options: ConfigureStoreOptions): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() diff --git a/src/createSlice.ts b/src/createSlice.ts index 85065c4d68..9811bf00cc 100644 --- a/src/createSlice.ts +++ b/src/createSlice.ts @@ -23,6 +23,10 @@ import { NoInfer } from './tsHelpers' */ export type SliceActionCreator

= PayloadActionCreator

+export type ActionsOfCaseReducersActions>> = { + [Type in keyof T]: ReturnType +}[keyof T] + /** * The return value of `createSlice` * @@ -41,13 +45,13 @@ export interface Slice< /** * The slice's reducer. */ - reducer: Reducer + reducer: Reducer>> /** * Action creators for the types of actions that are handled by the slice * reducer. */ - actions: CaseReducerActions + actions: CaseReducerActions /** * The individual case reducer functions that were passed in the `reducers` parameter. @@ -159,10 +163,15 @@ export type SliceCaseReducers = { * * @public */ -export type CaseReducerActions> = { - [Type in keyof CaseReducers]: CaseReducers[Type] extends { prepare: any } - ? ActionCreatorForCaseReducerWithPrepare - : ActionCreatorForCaseReducer +export type CaseReducerActions< + CaseReducers extends SliceCaseReducers, + SliceName extends string = string +> = { + [Type in string & keyof CaseReducers]: CaseReducers[Type] extends { + prepare: any + } + ? ActionCreatorForCaseReducerWithPrepare + : ActionCreatorForCaseReducer } /** @@ -171,22 +180,23 @@ export type CaseReducerActions> = { * @internal */ type ActionCreatorForCaseReducerWithPrepare< - CR extends { prepare: any } -> = _ActionCreatorWithPreparedPayload + CR extends { prepare: any }, + Type extends string = string +> = _ActionCreatorWithPreparedPayload /** * Get a `PayloadActionCreator` type for a passed `CaseReducer` * * @internal */ -type ActionCreatorForCaseReducer = CR extends ( - state: any, - action: infer Action -) => any +type ActionCreatorForCaseReducer< + CR, + Type extends string = string +> = CR extends (state: any, action: infer Action) => any ? Action extends { payload: infer P } - ? PayloadActionCreator

- : ActionCreatorWithoutPayload - : ActionCreatorWithoutPayload + ? PayloadActionCreator + : ActionCreatorWithoutPayload + : ActionCreatorWithoutPayload /** * Extracts the CaseReducers out of a `reducers` object, even if they are diff --git a/src/getDefaultMiddleware.ts b/src/getDefaultMiddleware.ts index 02eb736f10..8b983398aa 100644 --- a/src/getDefaultMiddleware.ts +++ b/src/getDefaultMiddleware.ts @@ -29,16 +29,17 @@ interface GetDefaultMiddlewareOptions { export type ThunkMiddlewareFor< S, - O extends GetDefaultMiddlewareOptions = {} + O extends GetDefaultMiddlewareOptions = {}, + A extends AnyAction = AnyAction > = O extends { thunk: false } ? never : O extends { thunk: { extraArgument: infer E } } - ? ThunkMiddleware + ? ThunkMiddleware : - | ThunkMiddleware //The ThunkMiddleware with a `null` ExtraArgument is here to provide backwards-compatibility. - | ThunkMiddleware + | ThunkMiddleware //The ThunkMiddleware with a `null` ExtraArgument is here to provide backwards-compatibility. + | ThunkMiddleware export type CurriedGetDefaultMiddleware = < O extends Partial = { diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts index 1039a8a48e..07a0adcfdb 100644 --- a/type-tests/files/createSlice.typetest.ts +++ b/type-tests/files/createSlice.typetest.ts @@ -1,4 +1,4 @@ -import { Action, AnyAction, Reducer } from 'redux' +import { Action, AnyAction, combineReducers, Reducer } from 'redux' import { ActionCreatorWithNonInferrablePayload, ActionCreatorWithOptionalPayload, @@ -68,8 +68,12 @@ const value = actionCreators.anyKey /* Reducer */ - const reducer: Reducer = slice.reducer + type InferredTypeActions = 'counter/increment' | 'counter/decrement' + const reducer: Reducer> = slice.reducer + + // @ts-expect-error + const stringTypeActionsReducer: Reducer = slice.reducer // @ts-expect-error const stringReducer: Reducer = slice.reducer // @ts-expect-error @@ -84,6 +88,40 @@ const value = actionCreators.anyKey slice.actions.other(1) } +/* + * Test: createSlice() safetly type actions with combineReducers + */ +{ + const firstAction = createAction<{ count: number }>('FIRST_ACTION') + + const slice = createSlice({ + name: 'counter', + initialState: 0, + reducers: { + increment: (state: number, action) => state + action.payload, + decrement: (state: number, action) => state - action.payload + }, + extraReducers: { + [firstAction.type]: (state: number, action) => + state + action.payload.count + } + }) + + /* Reducer */ + + const reducer = combineReducers({ + counter: slice.reducer, + }) + + reducer({ counter: 0 }, { type: 'counter/decrement', payload: undefined }) + reducer({ counter: 0 }, { type: 'counter/increment', payload: undefined }) + + // @ts-expect-error + reducer({ counter: 0 }, { type: 'decrement', payload: undefined }) + // @ts-expect-error + reducer({ counter: 0 }, { type: 'any-string', payload: undefined }) +} + /* * Test: Slice action creator types are inferred. */ @@ -139,7 +177,7 @@ const value = actionCreators.anyKey } /* - * Test: Slice action creator types properties are "string" + * Test: Slice action creator types properties are `${sliceName}/${reducerPropertyName}` */ { const counter = createSlice({ @@ -159,10 +197,16 @@ const value = actionCreators.anyKey const t: string = counter.actions.decrement.type const u: string = counter.actions.multiply.type + const a: 'counter/increment' = counter.actions.increment.type + const b: 'counter/decrement' = counter.actions.decrement.type + const c: 'counter/multiply' = counter.actions.multiply.type + + // @ts-expect-error + const x: 'increment' = counter.actions.increment.type // @ts-expect-error - const x: 'counter/increment' = counter.actions.increment.type + const y: 'decrement' = counter.actions.decrement.type // @ts-expect-error - const y: 'increment' = counter.actions.increment.type + const z: 'multiply' = counter.actions.multiply.type } /* @@ -193,7 +237,7 @@ const value = actionCreators.anyKey } }) - expectType(counter.actions.incrementByStrLen('test').type) + expectType<'test/incrementByStrLen'>(counter.actions.incrementByStrLen('test').type) expectType(counter.actions.incrementByStrLen('test').payload) expectType(counter.actions.concatMetaStrLen('test').payload) expectType(counter.actions.concatMetaStrLen('test').meta) @@ -272,7 +316,7 @@ const value = actionCreators.anyKey name: 'counter', initialState: 0, reducers: { - increment(state, action: PayloadAction) { + increment(state, action) { return state + action.payload }, decrement: {