From 2caf2a147f0a4b7c748b8c7ea3b2720957740ec4 Mon Sep 17 00:00:00 2001
From: Matthieu Prat <matthieuprat@gmail.com>
Date: Tue, 12 Dec 2017 11:11:09 +0100
Subject: [PATCH] fix(connectObs): unsubscribe from observables when unmounting
 (#92)

---
 .prettierrc                      |  4 ++++
 src/__tests__/connectObs.test.js | 24 +++++++++++++++++++++++
 src/connectObs.js                | 33 ++++++++++++++++++++++----------
 3 files changed, 51 insertions(+), 10 deletions(-)
 create mode 100644 .prettierrc

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..4a4b3d5
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,4 @@
+semi: false
+trailingComma: all
+printWidth: 80
+singleQuote: true
diff --git a/src/__tests__/connectObs.test.js b/src/__tests__/connectObs.test.js
index 458fd19..07100d7 100644
--- a/src/__tests__/connectObs.test.js
+++ b/src/__tests__/connectObs.test.js
@@ -143,4 +143,28 @@ describe('connectObs', () => {
     )
     expect(wrapper.equals(<div className="foo" />)).toBeTruthy()
   })
+
+  it('unsubscribes from observables when the enhanced component is unmounted', () => {
+    const spy = jest.fn()
+    const spyUnsubscribe = observable =>
+      Rx.Observable.create(observer => {
+        const subscription = observable.subscribe(observer)
+        return () => {
+          spy()
+          subscription.unsubscribe()
+        }
+      })
+
+    const Component = compose(
+      connectObs(({ props$ }) => ({
+        foo: spyUnsubscribe(Rx.Observable.never()),
+        props: spyUnsubscribe(props$),
+      })),
+    )('div')
+
+    const wrapper = shallow(<Component />)
+    expect(spy).toHaveBeenCalledTimes(0)
+    wrapper.unmount()
+    expect(spy).toHaveBeenCalledTimes(2)
+  })
 })
diff --git a/src/connectObs.js b/src/connectObs.js
index 24b63c3..abce2cf 100644
--- a/src/connectObs.js
+++ b/src/connectObs.js
@@ -62,9 +62,11 @@ const connectObs = obsMapper =>
   withObs(observables => {
     const nextProps$ = createObservable(observer => {
       const obsMap = obsMapper(observables)
-      checkObsMap(obsMap)
-      let props
       const obsProps = {}
+      const obsSubscriptions = []
+      let props
+
+      checkObsMap(obsMap)
 
       const update = () => {
         if (props) {
@@ -84,23 +86,34 @@ const connectObs = obsMapper =>
           const observable = obsConfig.toESObservable(obsMap[key])
           checkObservable(observable, key)
           obsProps[key] = undefined
-          observable.subscribe({
+          const subscription = observable.subscribe({
             next(value) {
               obsProps[key] = value
               update()
             },
             error: asyncThrow,
           })
+
+          obsSubscriptions.push(subscription)
         }
       })
 
-      obsConfig.toESObservable(observables.props$).subscribe({
-        next(nextProps) {
-          props = nextProps
-          update()
-        },
-        error: asyncThrow,
-      })
+      const propsSubscription = obsConfig
+        .toESObservable(observables.props$)
+        .subscribe({
+          next(nextProps) {
+            props = nextProps
+            update()
+          },
+          error: asyncThrow,
+        })
+
+      return () => {
+        propsSubscription.unsubscribe()
+        obsSubscriptions.forEach(subscription => {
+          subscription.unsubscribe()
+        })
+      }
     })
 
     return { props$: nextProps$ }