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();