diff --git a/README.md b/README.md index 8d509a4..b44b4f3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ When submitting a bug report, make sure to include a repro. The best way to do - [`initialState`](#initialstate) - [`effects`](#effects) - [`initialize`](#initialize) + - [`finalize`](#finalize) - [`computed`](#computed) - [`middleware`](#middleware) - [`injectState`](#injectstate) @@ -909,6 +910,11 @@ Each state container can define a special effect called `initialize`. This effe Note: this effect will NOT be passed down to a component's children. +##### `finalize` + +`initialize` counterpart, invoked immediately before a component is unmounted and destroyed (browser only). + +Note: this effect will NOT be passed down to a component's children. #### `computed` diff --git a/spec/integration/finalize.spec.js b/spec/integration/finalize.spec.js new file mode 100644 index 0000000..32af9db --- /dev/null +++ b/spec/integration/finalize.spec.js @@ -0,0 +1,51 @@ +import React from "react"; +import { mount } from "enzyme"; + +import { provideState, injectState, softUpdate } from "freactal"; + + +const Child = () =>
; +const ChildWithState = injectState(Child); +const wrapChildWithState = provideState({}); +const StatefulChild = wrapChildWithState(ChildWithState); + +const Parent = ({ state: { renderChildren, finalized }, children }) => ( +
+
{ finalized ? "true" : "false" }
+ {renderChildren ? children : null} +
+); +const ParentWithState = injectState(Parent); +const wrapParentWithState = provideState({ + initialState: () => ({ + finalized: false + }), + effects: { + finalize: softUpdate(() => ({ finalized: true })) + } +}); +const StatefulParent = wrapParentWithState(ParentWithState); + +const Root = () => ( + + + +); +const wrapRootWithState = provideState({ + initialState: () => ({ + renderChildren: true + }), + effects: { + toggelChildren: softUpdate(state => ({ renderChildren: !state.renderChildren })) + } +}); +const StatefulRoot = wrapRootWithState(Root); + + +describe("finalize", () => { + it("parent's `finalize` is not called when a child is unmounted", async () => { + const el = mount(); + await el.instance().effects.toggelChildren(); + expect(el.find(".parent-finalized").text()).to.equal("false"); + }); +}); diff --git a/spec/unit/provide.spec.js b/spec/unit/provide.spec.js index 86c15a2..5e23a27 100644 --- a/spec/unit/provide.spec.js +++ b/spec/unit/provide.spec.js @@ -54,6 +54,17 @@ describe("state provider", () => { }); }); + describe("upon unmounting", () => { + it("invokes its `finalize` effect", () => { + return new Promise(resolve => { + const Stateful = getStateful({ + effects: { finalize: resolve } + }); + mount().unmount(); + }); + }); + }); + describe("context", () => { it("preserves parent freactal context where no conflicts exist", () => { const context = { diff --git a/src/effects.js b/src/effects.js index 6273826..3120556 100644 --- a/src/effects.js +++ b/src/effects.js @@ -17,7 +17,7 @@ export const getEffects = (hocState, effectDefs, parentEffects) => { .then(applyReducer); return memo; - }, Object.assign({}, parentEffects, { initialize: null })); + }, Object.assign({}, parentEffects, { initialize: null, finalize: null })); return effects; }; diff --git a/src/provide.js b/src/provide.js index 38091a7..a608929 100644 --- a/src/provide.js +++ b/src/provide.js @@ -37,6 +37,7 @@ export class BaseStatefulComponent extends Component { } componentWillUnmount () { + if (this.effects.finalize) { this.effects.finalize(); } // this.unsubscribe may be undefined due to an error in child render if (this.unsubscribe) { this.unsubscribe();