Skip to content

Commit 4106c04

Browse files
committed
Add selectSlice to slice instance
1 parent 60055ea commit 4106c04

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

packages/toolkit/src/createSlice.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
import { createReducer } from './createReducer'
1616
import type { ActionReducerMapBuilder } from './mapBuilders'
1717
import { executeReducerBuilderCallback } from './mapBuilders'
18-
import type { ActionFromMatcher, Id, Matcher, Tail } from './tsHelpers'
18+
import type { Id, Tail } from './tsHelpers'
1919
import type { InjectConfig } from './combineSlices'
2020
import type {
2121
AsyncThunk,
@@ -78,13 +78,14 @@ export interface Slice<
7878
/**
7979
* Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter)
8080
*/
81-
getSelectors(): Id<SliceDefinedSelectors<State, Selectors, State>>
81+
getSelectors(this: this): Id<SliceDefinedSelectors<State, Selectors, State>>
8282

8383
/**
8484
* Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
8585
*/
8686
getSelectors<RootState>(
87-
selectState: (rootState: RootState) => State
87+
this: this,
88+
selectState: (this: this, rootState: RootState) => State
8889
): Id<SliceDefinedSelectors<State, Selectors, RootState>>
8990

9091
/**
@@ -100,6 +101,7 @@ export interface Slice<
100101
* Inject slice into provided reducer (return value from `combineSlices`), and return injected slice.
101102
*/
102103
injectInto<NewReducerPath extends string = ReducerPath>(
104+
this: this,
103105
injectable: {
104106
inject: (
105107
slice: { reducerPath: string; reducer: Reducer },
@@ -108,6 +110,13 @@ export interface Slice<
108110
},
109111
config?: InjectIntoConfig<NewReducerPath>
110112
): InjectedSlice<State, CaseReducers, Name, NewReducerPath, Selectors>
113+
114+
/**
115+
* Select the slice state, using the slice's current reducerPath.
116+
*
117+
* Will throw an error if slice is not found.
118+
*/
119+
selectSlice(this: this, state: { [K in ReducerPath]: State }): State
111120
}
112121

113122
/**
@@ -134,7 +143,7 @@ interface InjectedSlice<
134143
* Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
135144
*/
136145
getSelectors<RootState>(
137-
selectState: (rootState: RootState) => State | undefined
146+
selectState: (this: this, rootState: RootState) => State | undefined
138147
): Id<SliceDefinedSelectors<State, Selectors, RootState>>
139148

140149
/**
@@ -149,6 +158,13 @@ interface InjectedSlice<
149158
{ [K in ReducerPath]?: State | undefined }
150159
>
151160
>
161+
162+
/**
163+
* Select the slice state, using the slice's current reducerPath.
164+
*
165+
* Returns initial state if slice is not found.
166+
*/
167+
selectSlice(state: { [K in ReducerPath]?: State | undefined }): State
152168
}
153169

154170
/**
@@ -660,10 +676,6 @@ export function createSlice<
660676
})
661677
}
662678

663-
const defaultSelectSlice = (
664-
rootState: { [K in ReducerPath]: State }
665-
): State => rootState[reducerPath]
666-
667679
const selectSelf = (state: State) => state
668680

669681
const injectedSelectorCache = new WeakMap<
@@ -704,7 +716,7 @@ export function createSlice<
704716
options.selectors ?? {}
705717
)) {
706718
cached[name] = (rootState: any, ...args: any[]) => {
707-
let sliceState = selectState(rootState)
719+
let sliceState = selectState.call(this, rootState)
708720
if (typeof sliceState === 'undefined') {
709721
// check if injectInto has been called
710722
if (this !== slice) {
@@ -722,19 +734,29 @@ export function createSlice<
722734
}
723735
return cached as any
724736
},
737+
selectSlice(state) {
738+
let sliceState = state[this.reducerPath]
739+
if (typeof sliceState === 'undefined') {
740+
// check if injectInto has been called
741+
if (this !== slice) {
742+
sliceState = this.getInitialState()
743+
} else if (process.env.NODE_ENV !== 'production') {
744+
throw new Error(
745+
'selectSlice returned undefined for an uninjected slice reducer'
746+
)
747+
}
748+
}
749+
return sliceState
750+
},
725751
get selectors() {
726-
return this.getSelectors(defaultSelectSlice)
752+
return this.getSelectors(this.selectSlice)
727753
},
728754
injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) {
729755
const reducerPath = pathOpt ?? this.reducerPath
730756
injectable.inject({ reducerPath, reducer: this.reducer }, config)
731-
const selectSlice = (state: any) => state[reducerPath]
732757
return {
733758
...this,
734759
reducerPath,
735-
get selectors() {
736-
return this.getSelectors(selectSlice)
737-
},
738760
} as any
739761
},
740762
}

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,6 @@ describe('createSlice', () => {
495495
increment: (state) => ++state,
496496
},
497497
selectors: {
498-
selectSlice: (state) => state,
499498
selectMultiple: (state, multiplier: number) => state * multiplier,
500499
},
501500
})
@@ -513,13 +512,13 @@ describe('createSlice', () => {
513512
const injectedSlice = slice.injectInto(combinedReducer)
514513

515514
// selector returns initial state if undefined in real state
516-
expect(injectedSlice.selectors.selectSlice(uninjectedState)).toBe(
515+
expect(injectedSlice.selectSlice(uninjectedState)).toBe(
517516
slice.getInitialState()
518517
)
519518

520519
const injectedState = combinedReducer(undefined, increment())
521520

522-
expect(injectedSlice.selectors.selectSlice(injectedState)).toBe(
521+
expect(injectedSlice.selectSlice(injectedState)).toBe(
523522
slice.getInitialState() + 1
524523
)
525524
})
@@ -532,7 +531,6 @@ describe('createSlice', () => {
532531
increment: (state) => ++state,
533532
},
534533
selectors: {
535-
selectSlice: (state) => state,
536534
selectMultiple: (state, multiplier: number) => state * multiplier,
537535
},
538536
})
@@ -551,19 +549,25 @@ describe('createSlice', () => {
551549

552550
const injectedState = combinedReducer(undefined, increment())
553551

554-
expect(injected.selectors.selectSlice(injectedState)).toBe(
552+
expect(injected.selectSlice(injectedState)).toBe(
555553
slice.getInitialState() + 1
556554
)
555+
expect(injected.selectors.selectMultiple(injectedState, 2)).toBe(
556+
(slice.getInitialState() + 1) * 2
557+
)
557558

558559
const injected2 = slice.injectInto(combinedReducer, {
559560
reducerPath: 'injected2',
560561
})
561562

562563
const injected2State = combinedReducer(undefined, increment())
563564

564-
expect(injected2.selectors.selectSlice(injected2State)).toBe(
565+
expect(injected2.selectSlice(injected2State)).toBe(
565566
slice.getInitialState() + 1
566567
)
568+
expect(injected2.selectors.selectMultiple(injected2State, 2)).toBe(
569+
(slice.getInitialState() + 1) * 2
570+
)
567571
})
568572
})
569573
describe('reducers definition with asyncThunks', () => {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,3 +840,12 @@ const value = actionCreators.anyKey
840840
expectType<ActionCreatorWithPayload<string>>(wrappedSlice.actions.success)
841841
expectType<ActionCreatorWithoutPayload<string>>(wrappedSlice.actions.magic)
842842
}
843+
844+
/**
845+
* Test: selectSlice
846+
*/
847+
{
848+
expectType<number>(counterSlice.selectSlice({ counter: 0 }))
849+
// @ts-expect-error
850+
counterSlice.selectSlice({})
851+
}

0 commit comments

Comments
 (0)