add reactions to the updux

This commit is contained in:
Yanick Champoux 2022-08-30 14:13:59 -04:00
parent 0f3aeff396
commit 702a3e76d4
2 changed files with 116 additions and 2 deletions

View File

@ -4,7 +4,7 @@ import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
import { buildSelectors } from './selectors.js'; import { buildSelectors } from './selectors.js';
import { buildUpreducer } from './upreducer.js'; import { buildUpreducer } from './upreducer.js';
import { buildMiddleware } from './middleware.js'; import { buildMiddleware, augmentMiddlewareApi } from './middleware.js';
import { action, isActionGen } from './actions.js'; import { action, isActionGen } from './actions.js';
/** /**
@ -21,6 +21,7 @@ export class Updux {
#config = {}; #config = {};
#selectors = {}; #selectors = {};
#effects = []; #effects = [];
#localReactions = [];
constructor(config = {}) { constructor(config = {}) {
this.#config = config; this.#config = config;
@ -49,6 +50,8 @@ export class Updux {
} else if (R.isObject(config.effects)) { } else if (R.isObject(config.effects)) {
this.#effects = Object.entries(config.effects); this.#effects = Object.entries(config.effects);
} }
this.#localReactions = config.reactions ?? [];
} }
#addSubduxActions(_slice, subdux) { #addSubduxActions(_slice, subdux) {
@ -171,7 +174,7 @@ export class Updux {
}; };
} }
//this.subscribeAll(store); this.subscribeAll(store);
return store; return store;
} }
@ -179,6 +182,67 @@ export class Updux {
addEffect(action, effect) { addEffect(action, effect) {
this.#effects = [...this.#effects, [action, effect]]; this.#effects = [...this.#effects, [action, effect]];
} }
addReaction( reaction ) {
this.#localReactions = [ ...this.#localReactions, reaction ]
}
subscribeTo(store, subscription) {
const localStore = augmentMiddlewareApi(
{
...store,
subscribe: (subscriber) =>
this.subscribeTo(store, subscriber), // TODO not sure
},
this.actions,
this.selectors
);
const subscriber = subscription(localStore);
let previous;
const memoSub = () => {
const state = store.getState();
if (state === previous) return;
let p = previous;
previous = state;
subscriber(state, p, unsub);
};
let ret = store.subscribe(memoSub);
const unsub = typeof ret === 'function' ? ret : ret.unsub;
return {
unsub,
subscriberMemoized: memoSub,
subscriber,
};
}
subscribeAll(store) {
let results = this.#localReactions.map((sub) =>
this.subscribeTo(store, sub)
);
for (const subdux in this.#subduxes) {
if (subdux !== '*') {
const localStore = {
...store,
getState: () => store.getState()[subdux],
};
results.push(this.#subduxes[subdux].subscribeAll(localStore));
}
}
return {
unsub: () => results.forEach(({ unsub }) => unsub()),
subscriberMemoized: () =>
results.forEach(({ subscriberMemoized}) => subscriberMemoized()),
subscriber: () =>
results.forEach(({ subscriber }) => subscriber()
),
};
}
} }
export const dux = (config) => new Updux(config); export const dux = (config) => new Updux(config);

50
src/reactions.test.js Normal file
View File

@ -0,0 +1,50 @@
import { test, expect, vi } from 'vitest';
import { Updux } from './Updux.js';
test('basic reactions', async () => {
const spyA = vi.fn();
const spyB = vi.fn();
const foo = new Updux({
initial: { i: 0 },
reactions: [() => spyA],
actions: { inc: null },
mutations: {
inc: () => (state) => ({ ...state, i: state.i + 1 }),
},
});
foo.addReaction((api) => spyB);
const store = foo.createStore();
store.dispatch.inc();
expect(spyA).toHaveBeenCalledOnce();
expect(spyB).toHaveBeenCalledOnce();
});
test('subduxes reactions', async () => {
const spyA = vi.fn();
const spyB = vi.fn();
const foo = new Updux({
subduxes: {
a: new Updux({
initial: 1,
reactions: [() => state => spyA(state)],
actions: { inc: null },
mutations: {
inc: () => (state) => state + 1,
},
}),
b: new Updux({ initial: 10, reactions: [() => spyB] }),
},
});
const store = foo.createStore();
store.dispatch.inc();
store.dispatch.inc();
expect(spyA).toHaveBeenCalledTimes(2);
expect(spyA).toHaveBeenCalledWith(3)
expect(spyB).toHaveBeenCalledOnce(); // the original inc initialized the state
});