-  
-   Notifications  You must be signed in to change notification settings 
- Fork 1.2k
Closed
Milestone
Description
I'm just writing this down so I get my head emptied out and see if what I'm thinking makes any sense
reducer.ts
import { combineSlices } from '@reduxjs/toolkit' import { sliceA } from 'fileA' import { sliceB } from 'fileB' import { lazySliceC } from 'fileC' import type { lazySliceD } from 'fileD' import { anotherReducer } from 'somewhere' export interface LazyLoadedSlices {} export const rootReducer = combineSlices(sliceA, sliceB, { another: anotherReducer, }).withLazyLoadedSlices<LazyLoadedSlices>() /*  results in a return type of  {  [sliceA.name]: SliceAState,  [sliceB.name]: SliceBState,  another: AnotherState,  [lazySliceC.name]?: SliceCState, // see fileC.ts to understand why this appears here  [lazySliceD.name]?: SliceDState, // see fileD.ts to understand why this appears here  }  */the "naive" approach
fileC.ts
import { rootReducer, RootState } from './reducer' import { createSlice } from '@reduxjs/toolkit' interface SliceCState { foo: string } declare module './reducer' { export interface LazyLoadedSlices { [lazySliceC.name]: SliceCState } } export const lazySliceC = createSlice({ /* ... */ }) /**  * Synchronously call `injectSlice` in file.  * This will add `lazySliceC.reducer` to `rootReducer`, but **not** trigger an action.  * `state.lazySliceC` will stay `undefined` until the next action is dispatched.  */ rootReducer.injectSlice(lazySliceC) // this will still error - `lazySliceC` is optional here const naiveSelectFoo = (state: RootState) => state.lazySliceC.foo /**  * `injectSlice` would not inject the slice again if it is referentially equal, so it could be called   * in multiple files to get a `rootReducer` with types that are aware that `lazySliceC` has already   * been injected  */ const selectFoo = rootReducer.injectSlice(lazySliceC).selector((state) => { /**  * `lazySlice` is guaranteed to not be `undefined` here.  * we wrap the selector and if it is called before another action has been dispatched  * (meaning `state.lazySlice` is still `undefined`), the selector will not be called with   * the real `state`, but with a modified copy (or a Proxy or whatever we can do here to keep  * it as stable as possible)  */ return state.lazySlice.foo })the next step (maybe in a later release?)
"integrated" approach - adding a selectors field to createSlice
 fileD.ts
import { rootReducer } from './reducer' import { createSlice, WithSlice } from '@reduxjs/toolkit' interface SliceDState { bar: string } declare module './reducer' { // new helper `WithSlice` export interface LazyLoadedSlices extends WithSlice<typeof lazySliceD> {} } export const _lazySliceD = createSlice({ /* ... */ selectors: { uppercaseBar: sliceState => sliceState.bar.uppercase() } }) // this will still error, as `state.lazySliceD` is still optional at this point _lazySliceD.selectors.uppercaseBar(state) // this will internally just call `rootReducer.injectSlice(_lazySliceD) const lazySliceD = _lazySliceD.injectInto(rootReducer) // this will now work even if no action has been dispatched yet, as `selectors` now have been wrapped // in a similar approach to `rootReducer.withSlice(lazySliceC).selector` lazySliceD.selectors.uppercaseBar(state)general interface
declare function combineSlices(...slices: Slice | Record<string, reducer>): Reducer<CombinedState> & { withLazyLoadedSlices<LazyLoadedSlices>() // returns same object with enhanced types - those slice states are now optional injectSlice(slice: Slice) // returns same object with enhanced types - that slice's state is now non-optional selector(selectorFn) } // additions to a `slice`: injectInto(inectableReducer) // additions to slice options: selectors: Record<string, SelectorFn> // this might have room for improvement - do we allow for selectors that also take other state parts into account here or do those need to be created outside? How do we allow for memoized selectors?Open questions:
- do we have any circular type problems anywhere here?
- will this work nicely with nested combineSlicecalls?
airjp73, orenklein and matinrcoTamasSzigetimatinrcoBardiamist
Metadata
Metadata
Assignees
Labels
No labels