From f1b7677f0f8e15a499a74adcce25d3788ba3e270 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Mon, 18 Oct 2021 10:54:28 -0400 Subject: [PATCH] add upreducerWrapper --- src/{Updux.test.js => Updux.test.ts} | 0 src/Updux.ts | 49 +++++++++++++++++++++++++--- src/actions.ts | 2 +- src/buildUpreducer.js | 11 +++++-- src/types.ts | 13 +++++++- 5 files changed, 66 insertions(+), 9 deletions(-) rename src/{Updux.test.js => Updux.test.ts} (100%) diff --git a/src/Updux.test.js b/src/Updux.test.ts similarity index 100% rename from src/Updux.test.js rename to src/Updux.test.ts diff --git a/src/Updux.ts b/src/Updux.ts index fbed23d..170a7af 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -11,7 +11,14 @@ import { action } from './actions'; import { buildUpreducer } from './buildUpreducer'; import { buildMiddleware, augmentMiddlewareApi } from './buildMiddleware'; -import { AggregateDuxActions, AggregateDuxState, Dict } from './types'; +import { + AggregateDuxActions, + AggregateDuxState, + Dict, + ItemsOf, + Reducer, + Upreducer, +} from './types'; /** * Configuration object typically passed to the constructor of the class Updux. @@ -70,6 +77,28 @@ export interface UpduxConfig< * reaction for the mapped dux. */ mappedReaction?: Function | boolean; + + /** + * Wrapping function for the upreducer to provides full customization. + * @example + * // if an action has the 'dontDoIt' meta flag, don't do anything + * const dux = new Updux({ + * ..., + * upreducerWrapper: (upreducer) => action => { + * if( action?.meta?.dontDoIt ) return state => state; + * return upreducer(action); + * } + * }) + */ + upreducerWrapper?: ( + upreducer: Upreducer< + AggregateDuxState, + ItemsOf> + > + ) => Upreducer< + AggregateDuxState, + ItemsOf> + >; } export class Updux< @@ -90,6 +119,7 @@ export class Updux< #reactions = []; #mappedSelectors = undefined; #mappedReaction = undefined; + #upreducerWrapper = undefined; constructor(config: UpduxConfig) { this.#initial = config.initial ?? {}; @@ -130,6 +160,8 @@ export class Updux< this.#reactions = config.reactions ?? []; this.#mappedReaction = config.mappedReaction; + + this.#upreducerWrapper = config.upreducerWrapper; } #memoInitial = moize(buildInitial); @@ -171,15 +203,22 @@ export class Updux< ); } - get upreducer() { + get upreducer(): Upreducer< + AggregateDuxState, + ItemsOf> + > { return this.#memoUpreducer( this.initial, this.#mutations, - this.#subduxes + this.#subduxes, + this.#upreducerWrapper ); } - get reducer() { + get reducer(): Reducer< + AggregateDuxState, + ItemsOf> + > { return (state, action) => this.upreducer(action)(state); } @@ -340,7 +379,7 @@ export class Updux< selectors: Record; actions: AggregateDuxActions; } = reduxCreateStore( - this.reducer, + this.reducer as any, initial ?? this.initial, enhancer ) as any; diff --git a/src/actions.ts b/src/actions.ts index 03d1328..9db8252 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,4 +1,4 @@ -export type Action = { +export type Action = { type: T; meta?: Record; } & { diff --git a/src/buildUpreducer.js b/src/buildUpreducer.js index e544a74..97f4f81 100644 --- a/src/buildUpreducer.js +++ b/src/buildUpreducer.js @@ -1,13 +1,18 @@ import u from 'updeep'; import { mapValues } from 'lodash'; -export function buildUpreducer(initial, mutations, subduxes = {}) { +export function buildUpreducer( + initial, + mutations, + subduxes = {}, + wrapper = undefined +) { const subReducers = Object.keys(subduxes).length > 0 ? mapValues(subduxes, ({ upreducer }) => upreducer) : null; - return (action) => (state) => { + const upreducer = (action) => (state) => { if (!action?.type) throw new Error('upreducer called with a bad action'); @@ -35,4 +40,6 @@ export function buildUpreducer(initial, mutations, subduxes = {}) { return a(action.payload, action)(newState); }; + + return wrapper ? wrapper(upreducer) : upreducer; } diff --git a/src/types.ts b/src/types.ts index d969524..def3484 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,16 @@ +import { Action } from './actions'; + export type Dict = Record; +export type Upreducer = ( + action: Action +) => (state: TState) => TState; + +export type Reducer = ( + state: TState | undefined, + action: Action +) => TState; + export type UnionToIntersection = ( U extends any ? (k: U) => void : never ) extends (k: infer I) => void @@ -31,7 +42,7 @@ export type AggregateDuxState = TState & type DuxActionsSubduxes = C extends object ? ActionsOf : unknown; -type ItemsOf = C extends object ? C[keyof C] : unknown; +export type ItemsOf = C extends object ? C[keyof C] : unknown; export type AggregateDuxActions = TActions & UnionToIntersection>>;