Skip to content

Commit 564f62f

Browse files
committed
avoid relying on this in createSlice
1 parent 904ef2b commit 564f62f

File tree

2 files changed

+78
-50
lines changed

2 files changed

+78
-50
lines changed

packages/toolkit/src/createSlice.ts

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,21 @@ export interface Slice<
8888
/**
8989
* Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
9090
*/
91-
getSelectors(this: this): Id<SliceDefinedSelectors<State, Selectors, State>>
91+
getSelectors(): Id<SliceDefinedSelectors<State, Selectors, State>>
9292

9393
/**
9494
* Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
9595
*/
9696
getSelectors<RootState>(
97-
this: this,
98-
selectState: (this: this, rootState: RootState) => State
97+
selectState: (rootState: RootState) => State
9998
): Id<SliceDefinedSelectors<State, Selectors, RootState>>
10099

101100
/**
102101
* Selectors that assume the slice's state is `rootState[slice.reducerPath]` (which is usually the case)
103102
*
104103
* Equivalent to `slice.getSelectors((state: RootState) => state[slice.reducerPath])`.
105104
*/
106-
selectors: Id<
105+
get selectors(): Id<
107106
SliceDefinedSelectors<State, Selectors, { [K in ReducerPath]: State }>
108107
>
109108

@@ -126,7 +125,7 @@ export interface Slice<
126125
*
127126
* Will throw an error if slice is not found.
128127
*/
129-
selectSlice(this: this, state: { [K in ReducerPath]: State }): State
128+
selectSlice(state: { [K in ReducerPath]: State }): State
130129
}
131130

132131
/**
@@ -153,15 +152,15 @@ interface InjectedSlice<
153152
* Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
154153
*/
155154
getSelectors<RootState>(
156-
selectState: (this: this, rootState: RootState) => State | undefined
155+
selectState: (rootState: RootState) => State | undefined
157156
): Id<SliceDefinedSelectors<State, Selectors, RootState>>
158157

159158
/**
160159
* Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case)
161160
*
162161
* Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`.
163162
*/
164-
selectors: Id<
163+
get selectors(): Id<
165164
SliceDefinedSelectors<
166165
State,
167166
Selectors,
@@ -740,8 +739,8 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
740739

741740
const selectSelf = (state: State) => state
742741

743-
const injectedSelectorCache = new WeakMap<
744-
Slice<State, CaseReducers, Name, ReducerPath, Selectors>,
742+
const injectedSelectorCache = new Map<
743+
string,
745744
WeakMap<
746745
(rootState: any) => State | undefined,
747746
Record<string, (rootState: any) => any>
@@ -750,23 +749,42 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
750749

751750
let _reducer: ReducerWithInitialState<State>
752751

753-
const slice: Slice<State, CaseReducers, Name, ReducerPath, Selectors> = {
754-
name,
755-
reducerPath,
756-
reducer(state, action) {
757-
if (!_reducer) _reducer = buildReducer()
752+
function reducer(state: State | undefined, action: UnknownAction) {
753+
if (!_reducer) _reducer = buildReducer()
758754

759-
return _reducer(state, action)
760-
},
761-
actions: context.actionCreators as any,
762-
caseReducers: context.sliceCaseReducersByName as any,
763-
getInitialState() {
764-
if (!_reducer) _reducer = buildReducer()
755+
return _reducer(state, action)
756+
}
765757

766-
return _reducer.getInitialState()
767-
},
768-
getSelectors(selectState: (rootState: any) => State = selectSelf) {
769-
const selectorCache = emplace(injectedSelectorCache, this, {
758+
function getInitialState() {
759+
if (!_reducer) _reducer = buildReducer()
760+
761+
return _reducer.getInitialState()
762+
}
763+
764+
function makeSelectorProps(
765+
reducerPath: ReducerPath,
766+
injected = false
767+
): Pick<
768+
Slice<State, CaseReducers, Name, ReducerPath, Selectors>,
769+
'getSelectors' | 'selectors' | 'selectSlice' | 'reducerPath'
770+
> {
771+
function selectSlice(state: { [K in ReducerPath]: State }) {
772+
let sliceState = state[reducerPath]
773+
if (typeof sliceState === 'undefined') {
774+
if (injected) {
775+
sliceState = getInitialState()
776+
} else if (process.env.NODE_ENV !== 'production') {
777+
throw new Error(
778+
'selectSlice returned undefined for an uninjected slice reducer'
779+
)
780+
}
781+
}
782+
return sliceState
783+
}
784+
function getSelectors(
785+
selectState: (rootState: any) => State = selectSelf
786+
) {
787+
const selectorCache = emplace(injectedSelectorCache, reducerPath, {
770788
insert: () => new WeakMap(),
771789
})
772790

@@ -777,39 +795,39 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
777795
options.selectors ?? {}
778796
)) {
779797
map[name] = wrapSelector(
780-
this,
798+
{ getInitialState },
781799
selector,
782800
selectState,
783-
this !== slice
801+
injected
784802
)
785803
}
786804
return map
787805
},
788806
}) as any
789-
},
790-
selectSlice(state) {
791-
let sliceState = state[this.reducerPath]
792-
if (typeof sliceState === 'undefined') {
793-
// check if injectInto has been called
794-
if (this !== slice) {
795-
sliceState = this.getInitialState()
796-
} else if (process.env.NODE_ENV !== 'production') {
797-
throw new Error(
798-
'selectSlice returned undefined for an uninjected slice reducer'
799-
)
800-
}
801-
}
802-
return sliceState
803-
},
804-
get selectors() {
805-
return this.getSelectors(this.selectSlice)
806-
},
807+
}
808+
return {
809+
reducerPath,
810+
getSelectors,
811+
get selectors() {
812+
return getSelectors(selectSlice)
813+
},
814+
selectSlice,
815+
}
816+
}
817+
818+
const slice: Slice<State, CaseReducers, Name, ReducerPath, Selectors> = {
819+
name,
820+
reducer,
821+
actions: context.actionCreators as any,
822+
caseReducers: context.sliceCaseReducersByName as any,
823+
getInitialState,
824+
...makeSelectorProps(reducerPath),
807825
injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) {
808-
const reducerPath = pathOpt ?? this.reducerPath
809-
injectable.inject({ reducerPath, reducer: this.reducer }, config)
826+
const newReducerPath = pathOpt ?? reducerPath
827+
injectable.inject({ reducerPath: newReducerPath, reducer }, config)
810828
return {
811-
...this,
812-
reducerPath,
829+
...slice,
830+
...makeSelectorProps(newReducerPath as any, true),
813831
} as any
814832
},
815833
}
@@ -818,13 +836,13 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
818836
}
819837

820838
function wrapSelector<State, NewState, S extends Selector<State>>(
821-
slice: Slice,
839+
slice: { getInitialState(): State },
822840
selector: S,
823841
selectState: Selector<NewState, State>,
824842
injected?: boolean
825843
) {
826844
function wrapper(rootState: NewState, ...args: any[]) {
827-
let sliceState = selectState.call(slice, rootState)
845+
let sliceState = selectState(rootState)
828846
if (typeof sliceState === 'undefined') {
829847
if (injected) {
830848
sliceState = slice.getInitialState()

packages/toolkit/src/tests/createSlice.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,16 @@ describe('createSlice', () => {
493493
it('allows accessing properties on the selector', () => {
494494
expect(slice.selectors.selectMultiple.unwrapped.test).toBe(0)
495495
})
496+
it('has selectSlice attached to slice, which can go without this', () => {
497+
const slice = createSlice({
498+
name: 'counter',
499+
initialState: 42,
500+
reducers: {},
501+
})
502+
const { selectSlice } = slice
503+
expect(() => selectSlice({ counter: 42 })).not.toThrow()
504+
expect(selectSlice({ counter: 42 })).toBe(42)
505+
})
496506
})
497507
describe('slice injections', () => {
498508
it('uses injectInto to inject slice into combined reducer', () => {

0 commit comments

Comments
 (0)