import type { UnknownAction, Reducer, StateFromReducersMapObject } from 'redux'; import type { Id, NonUndefined, Tail, UnionToIntersection, WithOptionalProp } from './tsHelpers'; type SliceLike = { reducerPath: ReducerPath; reducer: Reducer; }; type AnySliceLike = SliceLike; type SliceLikeReducerPath = A extends SliceLike ? ReducerPath : never; type SliceLikeState = A extends SliceLike ? State : never; export type WithSlice = { [Path in SliceLikeReducerPath]: SliceLikeState; }; type ReducerMap = Record; type ExistingSliceLike = { [ReducerPath in keyof DeclaredState]: SliceLike>; }[keyof DeclaredState]; export type InjectConfig = { /** * 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. */ overrideExisting?: boolean; }; /** * A reducer that allows for slices/reducers to be injected after initialisation. */ export interface CombinedSliceReducer extends Reducer> { /** * Provide a type for slices that will be injected lazily. * * One way to do this would be with interface merging: * ```ts * * export interface LazyLoadedSlices {} * * export const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices(); * * // elsewhere * * declare module './reducer' { * export interface LazyLoadedSlices extends WithSlice {} * } * * const withBoolean = rootReducer.inject(booleanSlice); * * // elsewhere again * * declare module './reducer' { * export interface LazyLoadedSlices { * customName: CustomState * } * } * * const withCustom = rootReducer.inject({ reducerPath: "customName", reducer: customSlice.reducer }) * ``` */ withLazyLoadedSlices(): CombinedSliceReducer>>; /** * Inject a slice. * * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object. * * ```ts * rootReducer.inject(booleanSlice) * rootReducer.inject(baseApi) * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ inject>>(slice: Sl, config?: InjectConfig): CombinedSliceReducer>>; /** * Inject a slice. * * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object. * * ```ts * rootReducer.inject(booleanSlice) * rootReducer.inject(baseApi) * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ inject(slice: SliceLike, config?: InjectConfig): CombinedSliceReducer>>>; /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. * * ```ts * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; * // ^? boolean * }) * ``` * * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state. * * ```ts * * export interface LazyLoadedSlices {}; * * export const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices(); * * export const rootReducer = combineSlices({ inner: innerReducer }); * * export type RootState = ReturnType; * * // elsewhere * * declare module "./reducer.ts" { * export interface LazyLoadedSlices extends WithSlice {} * } * * const withBool = innerReducer.inject(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, * (rootState: RootState) => state.inner * ); * // now expects to be passed RootState instead of innerReducer state * * ``` * * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean * }) * ``` */ selector: { /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. * * ```ts * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; * // ^? boolean * }) * ``` * * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // undefined * return state.boolean * }) * ``` */ unknown>(selectorFn: Selector): (state: WithOptionalProp[0], Exclude>, ...args: Tail>) => ReturnType; /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. * * ```ts * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; * // ^? boolean * }) * ``` * * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state. * * ```ts * * interface LazyLoadedSlices {}; * * const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices(); * * const rootReducer = combineSlices({ inner: innerReducer }); * * type RootState = ReturnType; * * // elsewhere * * declare module "./reducer.ts" { * interface LazyLoadedSlices extends WithSlice {} * } * * const withBool = innerReducer.inject(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, * (rootState: RootState) => state.inner * ); * // now expects to be passed RootState instead of innerReducer state * * ``` * * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean * }) * ``` */ unknown, RootState>(selectorFn: Selector, selectState: (rootState: RootState, ...args: Tail>) => WithOptionalProp[0], Exclude>): (state: RootState, ...args: Tail>) => ReturnType; /** * Returns the unproxied state. Useful for debugging. * @param state state Proxy, that ensures injected reducers have value * @returns original, unproxied state * @throws if value passed is not a state Proxy */ original: (state: DeclaredState) => InitialState & Partial; }; } type InitialState> = UnionToIntersection : StateFromReducersMapObject : never>; export declare function combineSlices>(...slices: Slices): CombinedSliceReducer>>; export {};