| [a762898] | 1 | import type {
|
|---|
| 2 | PreloadedStateShapeFromReducersMapObject,
|
|---|
| 3 | Reducer,
|
|---|
| 4 | StateFromReducersMapObject,
|
|---|
| 5 | UnknownAction,
|
|---|
| 6 | } from 'redux'
|
|---|
| 7 | import { combineReducers } from 'redux'
|
|---|
| 8 | import { nanoid } from './nanoid'
|
|---|
| 9 | import type {
|
|---|
| 10 | Id,
|
|---|
| 11 | NonUndefined,
|
|---|
| 12 | Tail,
|
|---|
| 13 | UnionToIntersection,
|
|---|
| 14 | WithOptionalProp,
|
|---|
| 15 | } from './tsHelpers'
|
|---|
| 16 | import { getOrInsertComputed } from './utils'
|
|---|
| 17 |
|
|---|
| 18 | type SliceLike<ReducerPath extends string, State, PreloadedState = State> = {
|
|---|
| 19 | reducerPath: ReducerPath
|
|---|
| 20 | reducer: Reducer<State, any, PreloadedState>
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | type AnySliceLike = SliceLike<string, any>
|
|---|
| 24 |
|
|---|
| 25 | type SliceLikeReducerPath<A extends AnySliceLike> =
|
|---|
| 26 | A extends SliceLike<infer ReducerPath, any> ? ReducerPath : never
|
|---|
| 27 |
|
|---|
| 28 | type SliceLikeState<A extends AnySliceLike> =
|
|---|
| 29 | A extends SliceLike<any, infer State, any> ? State : never
|
|---|
| 30 |
|
|---|
| 31 | type SliceLikePreloadedState<A extends AnySliceLike> =
|
|---|
| 32 | A extends SliceLike<any, any, infer PreloadedState> ? PreloadedState : never
|
|---|
| 33 |
|
|---|
| 34 | export type WithSlice<A extends AnySliceLike> = {
|
|---|
| 35 | [Path in SliceLikeReducerPath<A>]: SliceLikeState<A>
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | export type WithSlicePreloadedState<A extends AnySliceLike> = {
|
|---|
| 39 | [Path in SliceLikeReducerPath<A>]: SliceLikePreloadedState<A>
|
|---|
| 40 | }
|
|---|
| 41 |
|
|---|
| 42 | type ReducerMap = Record<string, Reducer>
|
|---|
| 43 |
|
|---|
| 44 | type ExistingSliceLike<DeclaredState, PreloadedState> = {
|
|---|
| 45 | [ReducerPath in keyof DeclaredState]: SliceLike<
|
|---|
| 46 | ReducerPath & string,
|
|---|
| 47 | NonUndefined<DeclaredState[ReducerPath]>,
|
|---|
| 48 | NonUndefined<PreloadedState[ReducerPath & keyof PreloadedState]>
|
|---|
| 49 | >
|
|---|
| 50 | }[keyof DeclaredState]
|
|---|
| 51 |
|
|---|
| 52 | export type InjectConfig = {
|
|---|
| 53 | /**
|
|---|
| 54 | * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used.
|
|---|
| 55 | */
|
|---|
| 56 | overrideExisting?: boolean
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | /**
|
|---|
| 60 | * A reducer that allows for slices/reducers to be injected after initialisation.
|
|---|
| 61 | */
|
|---|
| 62 | export interface CombinedSliceReducer<
|
|---|
| 63 | InitialState,
|
|---|
| 64 | DeclaredState extends InitialState = InitialState,
|
|---|
| 65 | PreloadedState extends Partial<
|
|---|
| 66 | Record<keyof PreloadedState, any>
|
|---|
| 67 | > = Partial<DeclaredState>,
|
|---|
| 68 | > extends Reducer<DeclaredState, UnknownAction, PreloadedState> {
|
|---|
| 69 | /**
|
|---|
| 70 | * Provide a type for slices that will be injected lazily.
|
|---|
| 71 | *
|
|---|
| 72 | * One way to do this would be with interface merging:
|
|---|
| 73 | * ```ts
|
|---|
| 74 | *
|
|---|
| 75 | * export interface LazyLoadedSlices {}
|
|---|
| 76 | *
|
|---|
| 77 | * export const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|---|
| 78 | *
|
|---|
| 79 | * // elsewhere
|
|---|
| 80 | *
|
|---|
| 81 | * declare module './reducer' {
|
|---|
| 82 | * export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|---|
| 83 | * }
|
|---|
| 84 | *
|
|---|
| 85 | * const withBoolean = rootReducer.inject(booleanSlice);
|
|---|
| 86 | *
|
|---|
| 87 | * // elsewhere again
|
|---|
| 88 | *
|
|---|
| 89 | * declare module './reducer' {
|
|---|
| 90 | * export interface LazyLoadedSlices {
|
|---|
| 91 | * customName: CustomState
|
|---|
| 92 | * }
|
|---|
| 93 | * }
|
|---|
| 94 | *
|
|---|
| 95 | * const withCustom = rootReducer.inject({ reducerPath: "customName", reducer: customSlice.reducer })
|
|---|
| 96 | * ```
|
|---|
| 97 | */
|
|---|
| 98 | withLazyLoadedSlices<Lazy = {}, LazyPreloaded = Lazy>(): CombinedSliceReducer<
|
|---|
| 99 | InitialState,
|
|---|
| 100 | Id<DeclaredState & Partial<Lazy>>,
|
|---|
| 101 | Id<PreloadedState & Partial<LazyPreloaded>>
|
|---|
| 102 | >
|
|---|
| 103 |
|
|---|
| 104 | /**
|
|---|
| 105 | * Inject a slice.
|
|---|
| 106 | *
|
|---|
| 107 | * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
|
|---|
| 108 | *
|
|---|
| 109 | * ```ts
|
|---|
| 110 | * rootReducer.inject(booleanSlice)
|
|---|
| 111 | * rootReducer.inject(baseApi)
|
|---|
| 112 | * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
|
|---|
| 113 | * ```
|
|---|
| 114 | *
|
|---|
| 115 | */
|
|---|
| 116 | inject<Sl extends Id<ExistingSliceLike<DeclaredState, PreloadedState>>>(
|
|---|
| 117 | slice: Sl,
|
|---|
| 118 | config?: InjectConfig,
|
|---|
| 119 | ): CombinedSliceReducer<
|
|---|
| 120 | InitialState,
|
|---|
| 121 | Id<DeclaredState & WithSlice<Sl>>,
|
|---|
| 122 | Id<PreloadedState & Partial<WithSlicePreloadedState<Sl>>>
|
|---|
| 123 | >
|
|---|
| 124 |
|
|---|
| 125 | /**
|
|---|
| 126 | * Inject a slice.
|
|---|
| 127 | *
|
|---|
| 128 | * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
|
|---|
| 129 | *
|
|---|
| 130 | * ```ts
|
|---|
| 131 | * rootReducer.inject(booleanSlice)
|
|---|
| 132 | * rootReducer.inject(baseApi)
|
|---|
| 133 | * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
|
|---|
| 134 | * ```
|
|---|
| 135 | *
|
|---|
| 136 | */
|
|---|
| 137 | inject<ReducerPath extends string, State, PreloadedState = State>(
|
|---|
| 138 | slice: SliceLike<
|
|---|
| 139 | ReducerPath,
|
|---|
| 140 | State & (ReducerPath extends keyof DeclaredState ? never : State),
|
|---|
| 141 | PreloadedState &
|
|---|
| 142 | (ReducerPath extends keyof PreloadedState ? never : PreloadedState)
|
|---|
| 143 | >,
|
|---|
| 144 | config?: InjectConfig,
|
|---|
| 145 | ): CombinedSliceReducer<
|
|---|
| 146 | InitialState,
|
|---|
| 147 | Id<DeclaredState & WithSlice<SliceLike<ReducerPath, State>>>,
|
|---|
| 148 | Id<
|
|---|
| 149 | PreloadedState &
|
|---|
| 150 | WithSlicePreloadedState<SliceLike<ReducerPath, State, PreloadedState>>
|
|---|
| 151 | >
|
|---|
| 152 | >
|
|---|
| 153 |
|
|---|
| 154 | /**
|
|---|
| 155 | * Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|---|
| 156 | *
|
|---|
| 157 | * ```ts
|
|---|
| 158 | * const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|---|
| 159 | * // ^? boolean | undefined
|
|---|
| 160 | *
|
|---|
| 161 | * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|---|
| 162 | * // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|---|
| 163 | * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|---|
| 164 | * return state.boolean;
|
|---|
| 165 | * // ^? boolean
|
|---|
| 166 | * })
|
|---|
| 167 | * ```
|
|---|
| 168 | *
|
|---|
| 169 | * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
|
|---|
| 170 | *
|
|---|
| 171 | * ```ts
|
|---|
| 172 | *
|
|---|
| 173 | * export interface LazyLoadedSlices {};
|
|---|
| 174 | *
|
|---|
| 175 | * export const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|---|
| 176 | *
|
|---|
| 177 | * export const rootReducer = combineSlices({ inner: innerReducer });
|
|---|
| 178 | *
|
|---|
| 179 | * export type RootState = ReturnType<typeof rootReducer>;
|
|---|
| 180 | *
|
|---|
| 181 | * // elsewhere
|
|---|
| 182 | *
|
|---|
| 183 | * declare module "./reducer.ts" {
|
|---|
| 184 | * export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|---|
| 185 | * }
|
|---|
| 186 | *
|
|---|
| 187 | * const withBool = innerReducer.inject(booleanSlice);
|
|---|
| 188 | *
|
|---|
| 189 | * const selectBoolean = withBool.selector(
|
|---|
| 190 | * (state) => state.boolean,
|
|---|
| 191 | * (rootState: RootState) => state.inner
|
|---|
| 192 | * );
|
|---|
| 193 | * // now expects to be passed RootState instead of innerReducer state
|
|---|
| 194 | *
|
|---|
| 195 | * ```
|
|---|
| 196 | *
|
|---|
| 197 | * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|---|
| 198 | *
|
|---|
| 199 | * ```ts
|
|---|
| 200 | * const injectedReducer = rootReducer.inject(booleanSlice);
|
|---|
| 201 | * const selectBoolean = injectedReducer.selector((state) => {
|
|---|
| 202 | * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
|
|---|
| 203 | * return state.boolean
|
|---|
| 204 | * })
|
|---|
| 205 | * ```
|
|---|
| 206 | */
|
|---|
| 207 | selector: {
|
|---|
| 208 | /**
|
|---|
| 209 | * Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|---|
| 210 | *
|
|---|
| 211 | * ```ts
|
|---|
| 212 | * const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|---|
| 213 | * // ^? boolean | undefined
|
|---|
| 214 | *
|
|---|
| 215 | * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|---|
| 216 | * // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|---|
| 217 | * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|---|
| 218 | * return state.boolean;
|
|---|
| 219 | * // ^? boolean
|
|---|
| 220 | * })
|
|---|
| 221 | * ```
|
|---|
| 222 | *
|
|---|
| 223 | * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|---|
| 224 | *
|
|---|
| 225 | * ```ts
|
|---|
| 226 | * const injectedReducer = rootReducer.inject(booleanSlice);
|
|---|
| 227 | * const selectBoolean = injectedReducer.selector((state) => {
|
|---|
| 228 | * console.log(injectedReducer.selector.original(state).boolean) // undefined
|
|---|
| 229 | * return state.boolean
|
|---|
| 230 | * })
|
|---|
| 231 | * ```
|
|---|
| 232 | */
|
|---|
| 233 | <Selector extends (state: DeclaredState, ...args: any[]) => unknown>(
|
|---|
| 234 | selectorFn: Selector,
|
|---|
| 235 | ): (
|
|---|
| 236 | state: WithOptionalProp<
|
|---|
| 237 | Parameters<Selector>[0],
|
|---|
| 238 | Exclude<keyof DeclaredState, keyof InitialState>
|
|---|
| 239 | >,
|
|---|
| 240 | ...args: Tail<Parameters<Selector>>
|
|---|
| 241 | ) => ReturnType<Selector>
|
|---|
| 242 |
|
|---|
| 243 | /**
|
|---|
| 244 | * Create a selector that guarantees that the slices injected will have a defined value when selector is run.
|
|---|
| 245 | *
|
|---|
| 246 | * ```ts
|
|---|
| 247 | * const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
|
|---|
| 248 | * // ^? boolean | undefined
|
|---|
| 249 | *
|
|---|
| 250 | * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
|
|---|
| 251 | * // if action hasn't been dispatched since slice was injected, this would usually be undefined
|
|---|
| 252 | * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
|
|---|
| 253 | * return state.boolean;
|
|---|
| 254 | * // ^? boolean
|
|---|
| 255 | * })
|
|---|
| 256 | * ```
|
|---|
| 257 | *
|
|---|
| 258 | * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
|
|---|
| 259 | *
|
|---|
| 260 | * ```ts
|
|---|
| 261 | *
|
|---|
| 262 | * interface LazyLoadedSlices {};
|
|---|
| 263 | *
|
|---|
| 264 | * const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
|
|---|
| 265 | *
|
|---|
| 266 | * const rootReducer = combineSlices({ inner: innerReducer });
|
|---|
| 267 | *
|
|---|
| 268 | * type RootState = ReturnType<typeof rootReducer>;
|
|---|
| 269 | *
|
|---|
| 270 | * // elsewhere
|
|---|
| 271 | *
|
|---|
| 272 | * declare module "./reducer.ts" {
|
|---|
| 273 | * interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
|
|---|
| 274 | * }
|
|---|
| 275 | *
|
|---|
| 276 | * const withBool = innerReducer.inject(booleanSlice);
|
|---|
| 277 | *
|
|---|
| 278 | * const selectBoolean = withBool.selector(
|
|---|
| 279 | * (state) => state.boolean,
|
|---|
| 280 | * (rootState: RootState) => state.inner
|
|---|
| 281 | * );
|
|---|
| 282 | * // now expects to be passed RootState instead of innerReducer state
|
|---|
| 283 | *
|
|---|
| 284 | * ```
|
|---|
| 285 | *
|
|---|
| 286 | * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
|
|---|
| 287 | *
|
|---|
| 288 | * ```ts
|
|---|
| 289 | * const injectedReducer = rootReducer.inject(booleanSlice);
|
|---|
| 290 | * const selectBoolean = injectedReducer.selector((state) => {
|
|---|
| 291 | * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
|
|---|
| 292 | * return state.boolean
|
|---|
| 293 | * })
|
|---|
| 294 | * ```
|
|---|
| 295 | */
|
|---|
| 296 | <
|
|---|
| 297 | Selector extends (state: DeclaredState, ...args: any[]) => unknown,
|
|---|
| 298 | RootState,
|
|---|
| 299 | >(
|
|---|
| 300 | selectorFn: Selector,
|
|---|
| 301 | selectState: (
|
|---|
| 302 | rootState: RootState,
|
|---|
| 303 | ...args: Tail<Parameters<Selector>>
|
|---|
| 304 | ) => WithOptionalProp<
|
|---|
| 305 | Parameters<Selector>[0],
|
|---|
| 306 | Exclude<keyof DeclaredState, keyof InitialState>
|
|---|
| 307 | >,
|
|---|
| 308 | ): (
|
|---|
| 309 | state: RootState,
|
|---|
| 310 | ...args: Tail<Parameters<Selector>>
|
|---|
| 311 | ) => ReturnType<Selector>
|
|---|
| 312 | /**
|
|---|
| 313 | * Returns the unproxied state. Useful for debugging.
|
|---|
| 314 | * @param state state Proxy, that ensures injected reducers have value
|
|---|
| 315 | * @returns original, unproxied state
|
|---|
| 316 | * @throws if value passed is not a state Proxy
|
|---|
| 317 | */
|
|---|
| 318 | original: (state: DeclaredState) => InitialState & Partial<DeclaredState>
|
|---|
| 319 | }
|
|---|
| 320 | }
|
|---|
| 321 |
|
|---|
| 322 | type InitialState<Slices extends Array<AnySliceLike | ReducerMap>> =
|
|---|
| 323 | UnionToIntersection<
|
|---|
| 324 | Slices[number] extends infer Slice
|
|---|
| 325 | ? Slice extends AnySliceLike
|
|---|
| 326 | ? WithSlice<Slice>
|
|---|
| 327 | : StateFromReducersMapObject<Slice>
|
|---|
| 328 | : never
|
|---|
| 329 | >
|
|---|
| 330 |
|
|---|
| 331 | type InitialPreloadedState<Slices extends Array<AnySliceLike | ReducerMap>> =
|
|---|
| 332 | UnionToIntersection<
|
|---|
| 333 | Slices[number] extends infer Slice
|
|---|
| 334 | ? Slice extends AnySliceLike
|
|---|
| 335 | ? WithSlicePreloadedState<Slice>
|
|---|
| 336 | : PreloadedStateShapeFromReducersMapObject<Slice>
|
|---|
| 337 | : never
|
|---|
| 338 | >
|
|---|
| 339 |
|
|---|
| 340 | const isSliceLike = (
|
|---|
| 341 | maybeSliceLike: AnySliceLike | ReducerMap,
|
|---|
| 342 | ): maybeSliceLike is AnySliceLike =>
|
|---|
| 343 | 'reducerPath' in maybeSliceLike &&
|
|---|
| 344 | typeof maybeSliceLike.reducerPath === 'string'
|
|---|
| 345 |
|
|---|
| 346 | const getReducers = (slices: Array<AnySliceLike | ReducerMap>) =>
|
|---|
| 347 | slices.flatMap<[string, Reducer]>((sliceOrMap) =>
|
|---|
| 348 | isSliceLike(sliceOrMap)
|
|---|
| 349 | ? [[sliceOrMap.reducerPath, sliceOrMap.reducer]]
|
|---|
| 350 | : Object.entries(sliceOrMap),
|
|---|
| 351 | )
|
|---|
| 352 |
|
|---|
| 353 | const ORIGINAL_STATE = Symbol.for('rtk-state-proxy-original')
|
|---|
| 354 |
|
|---|
| 355 | const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE]
|
|---|
| 356 |
|
|---|
| 357 | const stateProxyMap = new WeakMap<object, object>()
|
|---|
| 358 |
|
|---|
| 359 | const createStateProxy = <State extends object>(
|
|---|
| 360 | state: State,
|
|---|
| 361 | reducerMap: Partial<Record<PropertyKey, Reducer>>,
|
|---|
| 362 | initialStateCache: Record<PropertyKey, unknown>,
|
|---|
| 363 | ) =>
|
|---|
| 364 | getOrInsertComputed(
|
|---|
| 365 | stateProxyMap,
|
|---|
| 366 | state,
|
|---|
| 367 | () =>
|
|---|
| 368 | new Proxy(state, {
|
|---|
| 369 | get: (target, prop, receiver) => {
|
|---|
| 370 | if (prop === ORIGINAL_STATE) return target
|
|---|
| 371 | const result = Reflect.get(target, prop, receiver)
|
|---|
| 372 | if (typeof result === 'undefined') {
|
|---|
| 373 | const cached = initialStateCache[prop]
|
|---|
| 374 | if (typeof cached !== 'undefined') return cached
|
|---|
| 375 | const reducer = reducerMap[prop]
|
|---|
| 376 | if (reducer) {
|
|---|
| 377 | // ensure action type is random, to prevent reducer treating it differently
|
|---|
| 378 | const reducerResult = reducer(undefined, { type: nanoid() })
|
|---|
| 379 | if (typeof reducerResult === 'undefined') {
|
|---|
| 380 | throw new Error(
|
|---|
| 381 | `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` +
|
|---|
| 382 | `If the state passed to the reducer is undefined, you must ` +
|
|---|
| 383 | `explicitly return the initial state. The initial state may ` +
|
|---|
| 384 | `not be undefined. If you don't want to set a value for this reducer, ` +
|
|---|
| 385 | `you can use null instead of undefined.`,
|
|---|
| 386 | )
|
|---|
| 387 | }
|
|---|
| 388 | initialStateCache[prop] = reducerResult
|
|---|
| 389 | return reducerResult
|
|---|
| 390 | }
|
|---|
| 391 | }
|
|---|
| 392 | return result
|
|---|
| 393 | },
|
|---|
| 394 | }),
|
|---|
| 395 | ) as State
|
|---|
| 396 |
|
|---|
| 397 | const original = (state: any) => {
|
|---|
| 398 | if (!isStateProxy(state)) {
|
|---|
| 399 | throw new Error('original must be used on state Proxy')
|
|---|
| 400 | }
|
|---|
| 401 | return state[ORIGINAL_STATE]
|
|---|
| 402 | }
|
|---|
| 403 |
|
|---|
| 404 | const emptyObject = {}
|
|---|
| 405 | const noopReducer: Reducer<Record<string, any>> = (state = emptyObject) => state
|
|---|
| 406 |
|
|---|
| 407 | export function combineSlices<Slices extends Array<AnySliceLike | ReducerMap>>(
|
|---|
| 408 | ...slices: Slices
|
|---|
| 409 | ): CombinedSliceReducer<
|
|---|
| 410 | Id<InitialState<Slices>>,
|
|---|
| 411 | Id<InitialState<Slices>>,
|
|---|
| 412 | Partial<Id<InitialPreloadedState<Slices>>>
|
|---|
| 413 | > {
|
|---|
| 414 | const reducerMap = Object.fromEntries(getReducers(slices))
|
|---|
| 415 |
|
|---|
| 416 | const getReducer = () =>
|
|---|
| 417 | Object.keys(reducerMap).length ? combineReducers(reducerMap) : noopReducer
|
|---|
| 418 |
|
|---|
| 419 | let reducer = getReducer()
|
|---|
| 420 |
|
|---|
| 421 | function combinedReducer(
|
|---|
| 422 | state: Record<string, unknown>,
|
|---|
| 423 | action: UnknownAction,
|
|---|
| 424 | ) {
|
|---|
| 425 | return reducer(state, action)
|
|---|
| 426 | }
|
|---|
| 427 |
|
|---|
| 428 | combinedReducer.withLazyLoadedSlices = () => combinedReducer
|
|---|
| 429 |
|
|---|
| 430 | const initialStateCache: Record<PropertyKey, unknown> = {}
|
|---|
| 431 |
|
|---|
| 432 | const inject = (
|
|---|
| 433 | slice: AnySliceLike,
|
|---|
| 434 | config: InjectConfig = {},
|
|---|
| 435 | ): typeof combinedReducer => {
|
|---|
| 436 | const { reducerPath, reducer: reducerToInject } = slice
|
|---|
| 437 |
|
|---|
| 438 | const currentReducer = reducerMap[reducerPath]
|
|---|
| 439 | if (
|
|---|
| 440 | !config.overrideExisting &&
|
|---|
| 441 | currentReducer &&
|
|---|
| 442 | currentReducer !== reducerToInject
|
|---|
| 443 | ) {
|
|---|
| 444 | if (
|
|---|
| 445 | typeof process !== 'undefined' &&
|
|---|
| 446 | process.env.NODE_ENV === 'development'
|
|---|
| 447 | ) {
|
|---|
| 448 | console.error(
|
|---|
| 449 | `called \`inject\` to override already-existing reducer ${reducerPath} without specifying \`overrideExisting: true\``,
|
|---|
| 450 | )
|
|---|
| 451 | }
|
|---|
| 452 |
|
|---|
| 453 | return combinedReducer
|
|---|
| 454 | }
|
|---|
| 455 |
|
|---|
| 456 | if (config.overrideExisting && currentReducer !== reducerToInject) {
|
|---|
| 457 | delete initialStateCache[reducerPath]
|
|---|
| 458 | }
|
|---|
| 459 |
|
|---|
| 460 | reducerMap[reducerPath] = reducerToInject
|
|---|
| 461 |
|
|---|
| 462 | reducer = getReducer()
|
|---|
| 463 |
|
|---|
| 464 | return combinedReducer
|
|---|
| 465 | }
|
|---|
| 466 |
|
|---|
| 467 | const selector = Object.assign(
|
|---|
| 468 | function makeSelector<State extends object, RootState, Args extends any[]>(
|
|---|
| 469 | selectorFn: (state: State, ...args: Args) => any,
|
|---|
| 470 | selectState?: (rootState: RootState, ...args: Args) => State,
|
|---|
| 471 | ) {
|
|---|
| 472 | return function selector(state: State, ...args: Args) {
|
|---|
| 473 | return selectorFn(
|
|---|
| 474 | createStateProxy(
|
|---|
| 475 | selectState ? selectState(state as any, ...args) : state,
|
|---|
| 476 | reducerMap,
|
|---|
| 477 | initialStateCache,
|
|---|
| 478 | ),
|
|---|
| 479 | ...args,
|
|---|
| 480 | )
|
|---|
| 481 | }
|
|---|
| 482 | },
|
|---|
| 483 | { original },
|
|---|
| 484 | )
|
|---|
| 485 |
|
|---|
| 486 | return Object.assign(combinedReducer, { inject, selector }) as any
|
|---|
| 487 | }
|
|---|