From 74c87432ad0a7768edad8b8907e8982dd8a536d6 Mon Sep 17 00:00:00 2001 From: Thibault Gouala Date: Thu, 13 Feb 2020 00:25:44 +0100 Subject: [PATCH 1/2] strongly type slice name --- etc/redux-toolkit.api.md | 10 ++--- src/createSlice.ts | 15 +++++--- type-tests/files/createSlice.typetest.ts | 47 ++++++++++++++++++++---- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 87ba8f4121..2d181dbf81 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -112,13 +112,13 @@ export { createSelector } export function createSerializableStateInvariantMiddleware(options?: SerializableStateInvariantMiddlewareOptions): Middleware; // @public -export function createSlice>(options: CreateSliceOptions): Slice; +export function createSlice>(options: CreateSliceOptions): Slice; // @public -export interface CreateSliceOptions = SliceCaseReducers> { +export interface CreateSliceOptions = SliceCaseReducers> { extraReducers?: CaseReducers, any> | ((builder: ActionReducerMapBuilder>) => void); initialState: State; - name: string; + name: Name; reducers: ValidateSliceCaseReducers; } @@ -182,10 +182,10 @@ export interface SerializableStateInvariantMiddlewareOptions { } // @public -export interface Slice = SliceCaseReducers> { +export interface Slice = SliceCaseReducers> { actions: CaseReducerActions; caseReducers: SliceDefinedCaseReducers; - name: string; + name: Name; reducer: Reducer; } diff --git a/src/createSlice.ts b/src/createSlice.ts index 6dfeefcdb4..d0df43c7fb 100644 --- a/src/createSlice.ts +++ b/src/createSlice.ts @@ -1,13 +1,13 @@ import { Reducer } from 'redux' import { + ActionCreatorWithoutPayload, createAction, PayloadAction, PayloadActionCreator, PrepareAction, - ActionCreatorWithoutPayload, _ActionCreatorWithPreparedPayload } from './createAction' -import { createReducer, CaseReducers, CaseReducer } from './createReducer' +import { CaseReducer, CaseReducers, createReducer } from './createReducer' import { ActionReducerMapBuilder, executeReducerBuilderCallback @@ -28,13 +28,14 @@ export type SliceActionCreator

= PayloadActionCreator

* @public */ export interface Slice< + Name extends string, State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers > { /** * The slice name. */ - name: string + name: Name /** * The slice's reducer. @@ -60,13 +61,14 @@ export interface Slice< * @public */ export interface CreateSliceOptions< + Name extends string, State = any, CR extends SliceCaseReducers = SliceCaseReducers > { /** * The slice's name. Used to namespace the generated action types. */ - name: string + name: Name /** * The initial state to be returned by the slice reducer. @@ -211,11 +213,12 @@ function getType(slice: string, actionKey: string): string { * @public */ export function createSlice< + Name extends string, State, CaseReducers extends SliceCaseReducers >( - options: CreateSliceOptions -): Slice { + options: CreateSliceOptions +): Slice { const { name, initialState } = options if (!name) { throw new Error('`name` is a required option for createSlice') diff --git a/type-tests/files/createSlice.typetest.ts b/type-tests/files/createSlice.typetest.ts index 7a1d7bb484..eff8ece953 100644 --- a/type-tests/files/createSlice.typetest.ts +++ b/type-tests/files/createSlice.typetest.ts @@ -1,22 +1,55 @@ -import { AnyAction, Reducer, Action } from 'redux' +import { Action, AnyAction, Reducer } from 'redux' +import { ValidateSliceCaseReducers } from 'src/createSlice' import { - createSlice, - PayloadAction, - createAction, - ActionReducerMapBuilder, - ActionCreatorWithOptionalPayload, ActionCreatorWithNonInferrablePayload, + ActionCreatorWithOptionalPayload, ActionCreatorWithoutPayload, ActionCreatorWithPayload, ActionCreatorWithPreparedPayload, + ActionReducerMapBuilder, + createAction, + createSlice, + PayloadAction, SliceCaseReducers } from '../../src' -import { ValidateSliceCaseReducers } from 'src/createSlice' function expectType(t: T) { return t } +/* + * Test: Slice name is strongly typed. + */ + +const counterSlice = createSlice({ + name: 'counter', + initialState: 0, + reducers: { + increment: (state: number, action) => state + action.payload, + decrement: (state: number, action) => state - action.payload + } +}) + +const uiSlice = createSlice({ + name: 'ui', + initialState: 0, + reducers: { + goToNext: (state: number, action) => state + action.payload, + goToPrevious: (state: number, action) => state - action.payload + } +}) + +const actionCreators = { + [counterSlice.name]: { ...counterSlice.actions }, + [uiSlice.name]: { ...uiSlice.actions } +} + +expectType(actionCreators.counter) +expectType(actionCreators.ui) + +// typings:expect-error +const value = actionCreators.anyKey + /* * Test: createSlice() infers the returned slice's type. */ From ff73b33dc361938c69821e72d8c57f9d2b22524b Mon Sep 17 00:00:00 2001 From: Thibault Gouala Date: Thu, 13 Feb 2020 22:51:48 +0100 Subject: [PATCH 2/2] move new generic to the end and default it to string --- etc/redux-toolkit.api.md | 6 +++--- src/createSlice.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 2d181dbf81..454c27f4d9 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -112,10 +112,10 @@ export { createSelector } export function createSerializableStateInvariantMiddleware(options?: SerializableStateInvariantMiddlewareOptions): Middleware; // @public -export function createSlice>(options: CreateSliceOptions): Slice; +export function createSlice, Name extends string = string>(options: CreateSliceOptions): Slice; // @public -export interface CreateSliceOptions = SliceCaseReducers> { +export interface CreateSliceOptions = SliceCaseReducers, Name extends string = string> { extraReducers?: CaseReducers, any> | ((builder: ActionReducerMapBuilder>) => void); initialState: State; name: Name; @@ -182,7 +182,7 @@ export interface SerializableStateInvariantMiddlewareOptions { } // @public -export interface Slice = SliceCaseReducers> { +export interface Slice = SliceCaseReducers, Name extends string = string> { actions: CaseReducerActions; caseReducers: SliceDefinedCaseReducers; name: Name; diff --git a/src/createSlice.ts b/src/createSlice.ts index d0df43c7fb..d074cda288 100644 --- a/src/createSlice.ts +++ b/src/createSlice.ts @@ -28,9 +28,9 @@ export type SliceActionCreator

= PayloadActionCreator

* @public */ export interface Slice< - Name extends string, State = any, - CaseReducers extends SliceCaseReducers = SliceCaseReducers + CaseReducers extends SliceCaseReducers = SliceCaseReducers, + Name extends string = string > { /** * The slice name. @@ -61,9 +61,9 @@ export interface Slice< * @public */ export interface CreateSliceOptions< - Name extends string, State = any, - CR extends SliceCaseReducers = SliceCaseReducers + CR extends SliceCaseReducers = SliceCaseReducers, + Name extends string = string > { /** * The slice's name. Used to namespace the generated action types. @@ -213,12 +213,12 @@ function getType(slice: string, actionKey: string): string { * @public */ export function createSlice< - Name extends string, State, - CaseReducers extends SliceCaseReducers + CaseReducers extends SliceCaseReducers, + Name extends string = string >( - options: CreateSliceOptions -): Slice { + options: CreateSliceOptions +): Slice { const { name, initialState } = options if (!name) { throw new Error('`name` is a required option for createSlice')