diff --git a/src/store.ts b/src/store.ts index ead4a763..ed2adcc9 100644 --- a/src/store.ts +++ b/src/store.ts @@ -75,6 +75,11 @@ export class Store extends Context { ); return result; } + + __notifyComponents(): Promise { + this.trigger("before-update"); + return super.__notifyComponents(); + } } interface SelectorOptions { @@ -105,13 +110,16 @@ export function useStore(selector, options: SelectorOptions = {}): any { const newRevNumber = hashFn(result); if ((newRevNumber > 0 && revNumber !== newRevNumber) || !isEqual(oldResult, result)) { revNumber = newRevNumber; - if (options.onUpdate) { - options.onUpdate(result); - } return true; } return false; } + if (options.onUpdate) { + store.on("before-update", component, () => { + const newValue = selector(store!.state, component.props!); + options.onUpdate(newValue); + }); + } store.updateFunctions[componentId].push(function (): boolean { return selectCompareUpdate(store!.state, component.props); }); @@ -132,6 +140,9 @@ export function useStore(selector, options: SelectorOptions = {}): any { const __destroy = component.__destroy; component.__destroy = (parent) => { delete store.updateFunctions[componentId]; + if (options.onUpdate) { + store.off("before-update", component); + } __destroy.call(component, parent); }; diff --git a/tests/store_hooks.test.ts b/tests/store_hooks.test.ts index 3a54d540..2ecc0548 100644 --- a/tests/store_hooks.test.ts +++ b/tests/store_hooks.test.ts @@ -571,12 +571,12 @@ describe("connecting a component to store", () => { app.state.beerId = 2; await nextTick(); expect(fixture.innerHTML).toBe("
kwak
"); - expect(counter).toBe(1); + expect(counter).toBe(0); store.dispatch("renameBeer", { id: 2, name: "orval" }); await nextTick(); expect(fixture.innerHTML).toBe("
orval
"); - expect(counter).toBe(2); + expect(counter).toBe(1); }); test("connected component is properly cleaned up on destroy", async () => { @@ -1345,4 +1345,58 @@ describe("various scenarios", () => { await nextTick(); expect(fixture.innerHTML).toBe("
testWorld
3
"); }); + + test("parent/children with store, parent is remounted", async () => { + const store = new Store({ state: { a: 1, b: 1 } }); + + class Child extends Component { + static template = xml`
`; + a: any; + constructor(parent, props) { + super(parent, props); + this.a = useStore( + (state, props) => { + return state.a; + }, + { + onUpdate: (a) => { + this.a = a; + }, + } + ); + } + } + + class Parent extends Component { + static template = xml` +
+ parent: + +
`; + static components = { Child }; + + b: any; + constructor(parent, props) { + super(parent, props); + this.b = useStore((state, props) => { + return state.b; + }); + } + } + (env as any).store = store; + + const div = document.createElement("div"); + fixture.appendChild(div); + + // initial mounting + const parent = await mount(Parent, { target: fixture, env }); + expect(fixture.innerHTML).toBe("
parent: 1
1
"); + + // remounting component, then immediately update store.state + parent.mount(div); + store.state.a++; + + await nextTick(); + expect(fixture.innerHTML).toBe("
parent: 1
2
"); + }); });