diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index ec1128b8a6..bc43c15ad4 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -15,7 +15,7 @@ import type { import { createReducer } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { ActionFromMatcher, Id, Matcher, Tail } from './tsHelpers' +import type { Id, Tail } from './tsHelpers' import type { InjectConfig } from './combineSlices' import type { AsyncThunk, @@ -78,13 +78,14 @@ export interface Slice< /** * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) */ - getSelectors(): Id> + getSelectors(this: this): Id> /** * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( - selectState: (rootState: RootState) => State + this: this, + selectState: (this: this, rootState: RootState) => State ): Id> /** @@ -100,6 +101,7 @@ export interface Slice< * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ injectInto( + this: this, injectable: { inject: ( slice: { reducerPath: string; reducer: Reducer }, @@ -108,6 +110,13 @@ export interface Slice< }, config?: InjectIntoConfig ): InjectedSlice + + /** + * Select the slice state, using the slice's current reducerPath. + * + * Will throw an error if slice is not found. + */ + selectSlice(this: this, state: { [K in ReducerPath]: State }): State } /** @@ -134,7 +143,7 @@ interface InjectedSlice< * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( - selectState: (rootState: RootState) => State | undefined + selectState: (this: this, rootState: RootState) => State | undefined ): Id> /** @@ -149,6 +158,13 @@ interface InjectedSlice< { [K in ReducerPath]?: State | undefined } > > + + /** + * Select the slice state, using the slice's current reducerPath. + * + * Returns initial state if slice is not found. + */ + selectSlice(state: { [K in ReducerPath]?: State | undefined }): State } /** @@ -660,10 +676,6 @@ export function createSlice< }) } - const defaultSelectSlice = ( - rootState: { [K in ReducerPath]: State } - ): State => rootState[reducerPath] - const selectSelf = (state: State) => state const injectedSelectorCache = new WeakMap< @@ -704,7 +716,7 @@ export function createSlice< options.selectors ?? {} )) { cached[name] = (rootState: any, ...args: any[]) => { - let sliceState = selectState(rootState) + let sliceState = selectState.call(this, rootState) if (typeof sliceState === 'undefined') { // check if injectInto has been called if (this !== slice) { @@ -722,19 +734,29 @@ export function createSlice< } return cached as any }, + selectSlice(state) { + let sliceState = state[this.reducerPath] + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectSlice returned undefined for an uninjected slice reducer' + ) + } + } + return sliceState + }, get selectors() { - return this.getSelectors(defaultSelectSlice) + return this.getSelectors(this.selectSlice) }, injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { const reducerPath = pathOpt ?? this.reducerPath injectable.inject({ reducerPath, reducer: this.reducer }, config) - const selectSlice = (state: any) => state[reducerPath] return { ...this, reducerPath, - get selectors() { - return this.getSelectors(selectSlice) - }, } as any }, } diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 302c06c9c9..4755a4e5dc 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -495,7 +495,6 @@ describe('createSlice', () => { increment: (state) => ++state, }, selectors: { - selectSlice: (state) => state, selectMultiple: (state, multiplier: number) => state * multiplier, }, }) @@ -513,13 +512,13 @@ describe('createSlice', () => { const injectedSlice = slice.injectInto(combinedReducer) // selector returns initial state if undefined in real state - expect(injectedSlice.selectors.selectSlice(uninjectedState)).toBe( + expect(injectedSlice.selectSlice(uninjectedState)).toBe( slice.getInitialState() ) const injectedState = combinedReducer(undefined, increment()) - expect(injectedSlice.selectors.selectSlice(injectedState)).toBe( + expect(injectedSlice.selectSlice(injectedState)).toBe( slice.getInitialState() + 1 ) }) @@ -532,7 +531,6 @@ describe('createSlice', () => { increment: (state) => ++state, }, selectors: { - selectSlice: (state) => state, selectMultiple: (state, multiplier: number) => state * multiplier, }, }) @@ -551,9 +549,12 @@ describe('createSlice', () => { const injectedState = combinedReducer(undefined, increment()) - expect(injected.selectors.selectSlice(injectedState)).toBe( + expect(injected.selectSlice(injectedState)).toBe( slice.getInitialState() + 1 ) + expect(injected.selectors.selectMultiple(injectedState, 2)).toBe( + (slice.getInitialState() + 1) * 2 + ) const injected2 = slice.injectInto(combinedReducer, { reducerPath: 'injected2', @@ -561,9 +562,12 @@ describe('createSlice', () => { const injected2State = combinedReducer(undefined, increment()) - expect(injected2.selectors.selectSlice(injected2State)).toBe( + expect(injected2.selectSlice(injected2State)).toBe( slice.getInitialState() + 1 ) + expect(injected2.selectors.selectMultiple(injected2State, 2)).toBe( + (slice.getInitialState() + 1) * 2 + ) }) }) describe('reducers definition with asyncThunks', () => { diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 9cd6371f0f..94ad5b773c 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -840,3 +840,12 @@ const value = actionCreators.anyKey expectType>(wrappedSlice.actions.success) expectType>(wrappedSlice.actions.magic) } + +/** + * Test: selectSlice + */ +{ + expectType(counterSlice.selectSlice({ counter: 0 })) + // @ts-expect-error + counterSlice.selectSlice({}) +}