Skip to content

Commit

Permalink
strongly type slice name (#354)
Browse files Browse the repository at this point in the history
* strongly type slice name

* move new generic to the end and default it to string
  • Loading branch information
tgouala committed Feb 16, 2020
1 parent 9842933 commit 762f161
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 21 deletions.
10 changes: 5 additions & 5 deletions etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ export { createSelector }
export function createSerializableStateInvariantMiddleware(options?: SerializableStateInvariantMiddlewareOptions): Middleware;

// @public
export function createSlice<State, CaseReducers extends SliceCaseReducers<State>>(options: CreateSliceOptions<State, CaseReducers>): Slice<State, CaseReducers>;
export function createSlice<State, CaseReducers extends SliceCaseReducers<State>, Name extends string = string>(options: CreateSliceOptions<State, CaseReducers, Name>): Slice<State, CaseReducers, Name>;

// @public
export interface CreateSliceOptions<State = any, CR extends SliceCaseReducers<State> = SliceCaseReducers<State>> {
export interface CreateSliceOptions<State = any, CR extends SliceCaseReducers<State> = SliceCaseReducers<State>, Name extends string = string> {
extraReducers?: CaseReducers<NoInfer<State>, any> | ((builder: ActionReducerMapBuilder<NoInfer<State>>) => void);
initialState: State;
name: string;
name: Name;
reducers: ValidateSliceCaseReducers<State, CR>;
}

Expand Down Expand Up @@ -182,10 +182,10 @@ export interface SerializableStateInvariantMiddlewareOptions {
}

// @public
export interface Slice<State = any, CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>> {
export interface Slice<State = any, CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>, Name extends string = string> {
actions: CaseReducerActions<CaseReducers>;
caseReducers: SliceDefinedCaseReducers<CaseReducers>;
name: string;
name: Name;
reducer: Reducer<State>;
}

Expand Down
21 changes: 12 additions & 9 deletions src/createSlice.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -30,12 +30,13 @@ export type SliceActionCreator<P> = PayloadActionCreator<P>
*/
export interface Slice<
State = any,
CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>
CaseReducers extends SliceCaseReducers<State> = SliceCaseReducers<State>,
Name extends string = string
> {
/**
* The slice name.
*/
name: string
name: Name

/**
* The slice's reducer.
Expand All @@ -62,12 +63,13 @@ export interface Slice<
*/
export interface CreateSliceOptions<
State = any,
CR extends SliceCaseReducers<State> = SliceCaseReducers<State>
CR extends SliceCaseReducers<State> = SliceCaseReducers<State>,
Name extends string = string
> {
/**
* 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.
Expand Down Expand Up @@ -213,10 +215,11 @@ function getType(slice: string, actionKey: string): string {
*/
export function createSlice<
State,
CaseReducers extends SliceCaseReducers<State>
CaseReducers extends SliceCaseReducers<State>,
Name extends string = string
>(
options: CreateSliceOptions<State, CaseReducers>
): Slice<State, CaseReducers> {
options: CreateSliceOptions<State, CaseReducers, Name>
): Slice<State, CaseReducers, Name> {
const { name, initialState } = options
if (!name) {
throw new Error('`name` is a required option for createSlice')
Expand Down
47 changes: 40 additions & 7 deletions type-tests/files/createSlice.typetest.ts
Original file line number Diff line number Diff line change
@@ -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: 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<typeof counterSlice.actions>(actionCreators.counter)
expectType<typeof uiSlice.actions>(actionCreators.ui)

// typings:expect-error
const value = actionCreators.anyKey

/*
* Test: createSlice() infers the returned slice's type.
*/
Expand Down

0 comments on commit 762f161

Please sign in to comment.