Skip to content

Commit 25a813e

Browse files
committed
Merge branch 'master' of https://github.com/reduxjs/redux-toolkit into combineslices-example
2 parents 34a617a + 72f867e commit 25a813e

File tree

16 files changed

+414
-86
lines changed

16 files changed

+414
-86
lines changed

docs/introduction/why-rtk-is-redux-today.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ See these pages to learn how to use "modern Redux" with Redux Toolkit:
2929

3030
:::
3131

32-
## How Redux Toolkit Is Different Than the Redux Core
32+
## How Redux Toolkit Is Different From the Redux Core
3333

3434
### What Is "Redux"?
3535

docs/rtk-query/usage/customizing-create-api.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ const customCreateApi = buildCreateApi(
4848
)
4949
```
5050

51+
## Customizing `createSelector` for RTKQ
52+
53+
Both `coreModule` and `reactHooksModule` accept a `createSelector` option which should be a selector creator instance from Reselect or with an equivalent signature.
54+
55+
```ts
56+
import * as React from 'react'
57+
import { createSelectorCreator, lruMemoize } from '@reduxjs/toolkit'
58+
import {
59+
buildCreateApi,
60+
coreModule,
61+
reactHooksModule,
62+
} from '@reduxjs/toolkit/query/react'
63+
64+
const createLruSelector = createSelectorCreator(lruMemoize)
65+
66+
const customCreateApi = buildCreateApi(
67+
coreModule({ createSelector: createLruSelector }),
68+
reactHooksModule({ createSelector: createLruSelector })
69+
)
70+
```
71+
5172
## Creating your own module
5273

5374
If you want to create your own module, you should review [the react-hooks module](https://github.com/reduxjs/redux-toolkit/blob/b74a52935a5840bebca5acdc8e2265e3b6497afa/src/query/react/module.ts) to see what an implementation would look like.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"prettier": "^2.2.1",
3939
"release-it": "^14.12.5",
4040
"serve": "^14.2.0",
41-
"typescript": "5.2"
41+
"typescript": "^5.2.2"
4242
},
4343
"resolutions": {
4444
"@babel/core": "7.19.3",
@@ -69,8 +69,7 @@
6969
"type-fest": "2.19.0",
7070
"[email protected]": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch",
7171
"@typescript-eslint/eslint-plugin": "6.12.0",
72-
"@typescript-eslint/parser": "6.12.0",
73-
"typescript": "5.2.2"
72+
"@typescript-eslint/parser": "6.12.0"
7473
},
7574
"scripts": {
7675
"build": "yarn build:packages",

packages/toolkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
}
5050
},
5151
"devDependencies": {
52-
"@arethetypeswrong/cli": "^0.13.1",
52+
"@arethetypeswrong/cli": "^0.13.5",
5353
"@microsoft/api-extractor": "^7.13.2",
5454
"@phryneas/ts-version": "^1.0.2",
5555
"@size-limit/preset-small-lib": "^4.11.0",

packages/toolkit/src/createSlice.ts

Lines changed: 69 additions & 51 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+
boolean,
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<CurrentReducerPath extends string = ReducerPath>(
765+
reducerPath: CurrentReducerPath,
766+
injected = false
767+
): Pick<
768+
Slice<State, CaseReducers, Name, CurrentReducerPath, Selectors>,
769+
'getSelectors' | 'selectors' | 'selectSlice' | 'reducerPath'
770+
> {
771+
function selectSlice(state: { [K in CurrentReducerPath]: 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, injected, {
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,
781798
selector,
782799
selectState,
783-
this !== slice
800+
getInitialState,
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, true),
813831
} as any
814832
},
815833
}
@@ -818,16 +836,16 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
818836
}
819837

820838
function wrapSelector<State, NewState, S extends Selector<State>>(
821-
slice: Slice,
822839
selector: S,
823840
selectState: Selector<NewState, State>,
841+
getInitialState: () => 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) {
830-
sliceState = slice.getInitialState()
848+
sliceState = getInitialState()
831849
} else if (process.env.NODE_ENV !== 'production') {
832850
throw new Error(
833851
'selectState returned undefined for an uninjected slice reducer'

packages/toolkit/src/listenerMiddleware/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
addAbortSignalListener,
4747
assertFunction,
4848
catchRejection,
49+
noop,
4950
} from './utils'
5051
export { TaskAbortError } from './exceptions'
5152
export type {
@@ -115,7 +116,7 @@ const createFork = (
115116
)
116117

117118
if (opts?.autoJoin) {
118-
parentBlockingPromises.push(result)
119+
parentBlockingPromises.push(result.catch(noop))
119120
}
120121

121122
return {
@@ -440,7 +441,7 @@ export const createListenerMiddleware = <
440441
})
441442
}
442443
} finally {
443-
await Promise.allSettled(autoJoinPromises)
444+
await Promise.all(autoJoinPromises)
444445

445446
abortControllerWithReason(internalTaskController, listenerCompleted) // Notify that the task has completed
446447
entry.pending.delete(internalTaskController)

packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ export const buildCacheLifecycleHandler: InternalHandlerBuilder = ({
253253

254254
function getCacheKey(action: any) {
255255
if (isQueryThunk(action)) return action.meta.arg.queryCacheKey
256-
if (isMutationThunk(action)) return action.meta.requestId
256+
if (isMutationThunk(action)) {
257+
return action.meta.arg.fixedCacheKey ?? action.meta.requestId
258+
}
257259
if (api.internalActions.removeQueryResult.match(action))
258260
return action.payload.queryCacheKey
259261
if (api.internalActions.removeMutationResult.match(action))

packages/toolkit/src/query/core/buildSelectors.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createNextState, createSelector } from './rtkImports'
1+
import type { createSelector as _createSelector } from './rtkImports'
2+
import { createNextState } from './rtkImports'
23
import type {
34
MutationSubState,
45
QuerySubState,
@@ -123,9 +124,11 @@ export function buildSelectors<
123124
>({
124125
serializeQueryArgs,
125126
reducerPath,
127+
createSelector,
126128
}: {
127129
serializeQueryArgs: InternalSerializeQueryArgs
128130
reducerPath: ReducerPath
131+
createSelector: typeof _createSelector
129132
}) {
130133
type RootState = _RootState<Definitions, string, string>
131134

packages/toolkit/src/query/core/module.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import type { ReferenceCacheLifecycle } from './buildMiddleware/cacheLifecycle'
4949
import type { ReferenceQueryLifecycle } from './buildMiddleware/queryLifecycle'
5050
import type { ReferenceCacheCollection } from './buildMiddleware/cacheCollection'
5151
import { enablePatches } from 'immer'
52+
import { createSelector as _createSelector } from './rtkImports'
5253

5354
/**
5455
* `ifOlderThan` - (default: `false` | `number`) - _number is value in seconds_
@@ -431,6 +432,13 @@ export type ListenerActions = {
431432

432433
export type InternalActions = SliceActions & ListenerActions
433434

435+
export interface CoreModuleOptions {
436+
/**
437+
* A selector creator (usually from `reselect`, or matching the same signature)
438+
*/
439+
createSelector?: typeof _createSelector
440+
}
441+
434442
/**
435443
* Creates a module containing the basic redux logic for use with `buildCreateApi`.
436444
*
@@ -439,7 +447,9 @@ export type InternalActions = SliceActions & ListenerActions
439447
* const createBaseApi = buildCreateApi(coreModule());
440448
* ```
441449
*/
442-
export const coreModule = (): Module<CoreModule> => ({
450+
export const coreModule = ({
451+
createSelector = _createSelector,
452+
}: CoreModuleOptions = {}): Module<CoreModule> => ({
443453
name: coreModuleName,
444454
init(
445455
api,
@@ -548,6 +558,7 @@ export const coreModule = (): Module<CoreModule> => ({
548558
} = buildSelectors({
549559
serializeQueryArgs: serializeQueryArgs as any,
550560
reducerPath,
561+
createSelector,
551562
})
552563

553564
safeAssign(api.util, { selectInvalidatedBy, selectCachedArgsForQuery })

packages/toolkit/src/query/react/buildHooks.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
ThunkAction,
55
ThunkDispatch,
66
} from '@reduxjs/toolkit'
7-
import { createSelector } from '@reduxjs/toolkit'
87
import type { DependencyList } from 'react'
98
import {
109
useCallback,
@@ -53,10 +52,7 @@ import { UNINITIALIZED_VALUE } from './constants'
5352
import { useShallowStableValue } from './useShallowStableValue'
5453
import type { BaseQueryFn } from '../baseQueryTypes'
5554
import { defaultSerializeQueryArgs } from '../defaultSerializeQueryArgs'
56-
import {
57-
InternalMiddlewareState,
58-
SubscriptionSelectors,
59-
} from '../core/buildMiddleware/types'
55+
import type { SubscriptionSelectors } from '../core/buildMiddleware/types'
6056

6157
// Copy-pasted from React-Redux
6258
export const useIsomorphicLayoutEffect =
@@ -582,6 +578,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
582578
batch,
583579
hooks: { useDispatch, useSelector, useStore },
584580
unstable__sideEffectsInRender,
581+
createSelector,
585582
},
586583
serializeQueryArgs,
587584
context,

0 commit comments

Comments
 (0)