Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add selectSlice to slice instance #3838

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions packages/toolkit/src/createSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<SliceDefinedSelectors<State, Selectors, State>>
getSelectors(this: this): Id<SliceDefinedSelectors<State, Selectors, State>>

/**
* Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state)
*/
getSelectors<RootState>(
selectState: (rootState: RootState) => State
this: this,
selectState: (this: this, rootState: RootState) => State
): Id<SliceDefinedSelectors<State, Selectors, RootState>>

/**
Expand All @@ -100,6 +101,7 @@ export interface Slice<
* Inject slice into provided reducer (return value from `combineSlices`), and return injected slice.
*/
injectInto<NewReducerPath extends string = ReducerPath>(
this: this,
injectable: {
inject: (
slice: { reducerPath: string; reducer: Reducer },
Expand All @@ -108,6 +110,13 @@ export interface Slice<
},
config?: InjectIntoConfig<NewReducerPath>
): InjectedSlice<State, CaseReducers, Name, NewReducerPath, Selectors>

/**
* 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
}

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

/**
Expand All @@ -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
}

/**
Expand Down Expand Up @@ -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<
Expand Down Expand Up @@ -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) {
Expand All @@ -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
},
}
Expand Down
16 changes: 10 additions & 6 deletions packages/toolkit/src/tests/createSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,6 @@ describe('createSlice', () => {
increment: (state) => ++state,
},
selectors: {
selectSlice: (state) => state,
selectMultiple: (state, multiplier: number) => state * multiplier,
},
})
Expand All @@ -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
)
})
Expand All @@ -532,7 +531,6 @@ describe('createSlice', () => {
increment: (state) => ++state,
},
selectors: {
selectSlice: (state) => state,
selectMultiple: (state, multiplier: number) => state * multiplier,
},
})
Expand All @@ -551,19 +549,25 @@ 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',
})

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', () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/toolkit/src/tests/createSlice.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,3 +840,12 @@ const value = actionCreators.anyKey
expectType<ActionCreatorWithPayload<string>>(wrappedSlice.actions.success)
expectType<ActionCreatorWithoutPayload<string>>(wrappedSlice.actions.magic)
}

/**
* Test: selectSlice
*/
{
expectType<number>(counterSlice.selectSlice({ counter: 0 }))
// @ts-expect-error
counterSlice.selectSlice({})
}
Loading