react-rx 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useAsObservable = void 0;
4
4
  var rxjs_1 = require("rxjs");
5
5
  var react_1 = require("react");
6
- var useIsomorphicEffect_1 = require("./useIsomorphicEffect");
7
6
  var operators_1 = require("rxjs/operators");
8
7
  function useAsObservable(value, operator) {
9
8
  var setup = (0, react_1.useCallback)(function () {
@@ -16,18 +15,28 @@ function useAsObservable(value, operator) {
16
15
  ref.current = setup();
17
16
  }
18
17
  var observable = ref.current[0];
19
- (0, useIsomorphicEffect_1.useIsomorphicEffect)(function () {
18
+ (0, react_1.useEffect)(function () {
20
19
  if (!ref.current) {
21
20
  return;
22
21
  }
23
22
  var _a = ref.current, subject = _a[1];
24
23
  subject.next(value);
25
24
  }, [value, ref]);
26
- (0, useIsomorphicEffect_1.useIsomorphicEffect)(function () {
25
+ var shouldRestoreSubscriptionRef = (0, react_1.useRef)(false);
26
+ (0, react_1.useEffect)(function () {
27
+ if (shouldRestoreSubscriptionRef.current) {
28
+ if (!ref.current) {
29
+ ref.current = setup();
30
+ }
31
+ shouldRestoreSubscriptionRef.current = false;
32
+ }
27
33
  return function () {
28
34
  if (!ref.current) {
29
35
  return;
30
36
  }
37
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
38
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
39
+ shouldRestoreSubscriptionRef.current = true;
31
40
  var _a = ref.current, subject = _a[1];
32
41
  subject.complete();
33
42
  ref.current = undefined;
@@ -1,2 +1,2 @@
1
- import { useLayoutEffect } from 'react';
2
- export declare const useIsomorphicEffect: typeof useLayoutEffect;
1
+ import { useEffect } from 'react';
2
+ export declare const useIsomorphicEffect: typeof useEffect;
@@ -4,7 +4,6 @@ exports.useMemoObservable = exports.useObservable = void 0;
4
4
  var react_1 = require("react");
5
5
  var shim_1 = require("use-sync-external-store/shim");
6
6
  var operators_1 = require("rxjs/operators");
7
- var useIsomorphicEffect_1 = require("./useIsomorphicEffect");
8
7
  function getValue(value) {
9
8
  return typeof value === 'function' ? value() : value;
10
9
  }
@@ -13,6 +12,7 @@ function getOrCreateStore(inputObservable, initialValue) {
13
12
  if (!cache.has(inputObservable)) {
14
13
  var entry_1 = { currentValue: initialValue };
15
14
  entry_1.observable = inputObservable.pipe((0, operators_1.shareReplay)({ refCount: true, bufferSize: 1 }), (0, operators_1.tap)(function (value) { return (entry_1.currentValue = value); }));
15
+ // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
16
16
  entry_1.subscription = entry_1.observable.subscribe();
17
17
  cache.set(inputObservable, entry_1);
18
18
  }
@@ -20,22 +20,38 @@ function getOrCreateStore(inputObservable, initialValue) {
20
20
  }
21
21
  function useObservable(observable, initialValue) {
22
22
  var _a = (0, react_1.useMemo)(function () {
23
- var record = getOrCreateStore(observable, getValue(initialValue));
23
+ var store = getOrCreateStore(observable, getValue(initialValue));
24
+ if (store.subscription.closed) {
25
+ store.subscription = store.observable.subscribe();
26
+ }
24
27
  return [
25
28
  function getSnapshot() {
26
- return record.currentValue;
29
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here to clear up some memory, as this subscription is only needed to provide a sync initialValue.
30
+ return store.currentValue;
27
31
  },
28
32
  function subscribe(callback) {
29
- var sub = record.observable.subscribe(function (next) { return callback(next); });
33
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
34
+ var sub = store.observable.subscribe(callback);
30
35
  return function () {
31
36
  sub.unsubscribe();
32
37
  };
33
38
  },
34
39
  ];
35
40
  }, [observable]), getSnapshot = _a[0], subscribe = _a[1];
36
- (0, useIsomorphicEffect_1.useIsomorphicEffect)(function () {
41
+ var shouldRestoreSubscriptionRef = (0, react_1.useRef)(false);
42
+ (0, react_1.useEffect)(function () {
43
+ var store = getOrCreateStore(observable, getValue(initialValue));
44
+ if (shouldRestoreSubscriptionRef.current) {
45
+ if (store.subscription.closed) {
46
+ store.subscription = store.observable.subscribe();
47
+ }
48
+ shouldRestoreSubscriptionRef.current = false;
49
+ }
37
50
  return function () {
38
- getOrCreateStore(observable, getValue(initialValue)).subscription.unsubscribe();
51
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
52
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
53
+ shouldRestoreSubscriptionRef.current = !store.subscription.closed;
54
+ store.subscription.unsubscribe();
39
55
  };
40
56
  }, [observable]);
41
57
  return (0, shim_1.useSyncExternalStore)(subscribe, getSnapshot);
@@ -1,6 +1,5 @@
1
1
  import { BehaviorSubject } from 'rxjs';
2
- import { useCallback, useRef } from 'react';
3
- import { useIsomorphicEffect } from './useIsomorphicEffect';
2
+ import { useCallback, useEffect, useRef } from 'react';
4
3
  import { distinctUntilChanged } from 'rxjs/operators';
5
4
  export function useAsObservable(value, operator) {
6
5
  const setup = useCallback(() => {
@@ -13,18 +12,28 @@ export function useAsObservable(value, operator) {
13
12
  ref.current = setup();
14
13
  }
15
14
  const [observable] = ref.current;
16
- useIsomorphicEffect(() => {
15
+ useEffect(() => {
17
16
  if (!ref.current) {
18
17
  return;
19
18
  }
20
19
  const [, subject] = ref.current;
21
20
  subject.next(value);
22
21
  }, [value, ref]);
23
- useIsomorphicEffect(() => {
22
+ const shouldRestoreSubscriptionRef = useRef(false);
23
+ useEffect(() => {
24
+ if (shouldRestoreSubscriptionRef.current) {
25
+ if (!ref.current) {
26
+ ref.current = setup();
27
+ }
28
+ shouldRestoreSubscriptionRef.current = false;
29
+ }
24
30
  return () => {
25
31
  if (!ref.current) {
26
32
  return;
27
33
  }
34
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
35
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
36
+ shouldRestoreSubscriptionRef.current = true;
28
37
  const [, subject] = ref.current;
29
38
  subject.complete();
30
39
  ref.current = undefined;
@@ -1,2 +1,2 @@
1
- import { useLayoutEffect } from 'react';
2
- export declare const useIsomorphicEffect: typeof useLayoutEffect;
1
+ import { useEffect } from 'react';
2
+ export declare const useIsomorphicEffect: typeof useEffect;
@@ -1,7 +1,6 @@
1
- import { useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
3
3
  import { shareReplay, tap } from 'rxjs/operators';
4
- import { useIsomorphicEffect } from './useIsomorphicEffect';
5
4
  function getValue(value) {
6
5
  return typeof value === 'function' ? value() : value;
7
6
  }
@@ -10,6 +9,7 @@ function getOrCreateStore(inputObservable, initialValue) {
10
9
  if (!cache.has(inputObservable)) {
11
10
  const entry = { currentValue: initialValue };
12
11
  entry.observable = inputObservable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap(value => (entry.currentValue = value)));
12
+ // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
13
13
  entry.subscription = entry.observable.subscribe();
14
14
  cache.set(inputObservable, entry);
15
15
  }
@@ -17,22 +17,38 @@ function getOrCreateStore(inputObservable, initialValue) {
17
17
  }
18
18
  export function useObservable(observable, initialValue) {
19
19
  const [getSnapshot, subscribe] = useMemo(() => {
20
- const record = getOrCreateStore(observable, getValue(initialValue));
20
+ const store = getOrCreateStore(observable, getValue(initialValue));
21
+ if (store.subscription.closed) {
22
+ store.subscription = store.observable.subscribe();
23
+ }
21
24
  return [
22
25
  function getSnapshot() {
23
- return record.currentValue;
26
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here to clear up some memory, as this subscription is only needed to provide a sync initialValue.
27
+ return store.currentValue;
24
28
  },
25
29
  function subscribe(callback) {
26
- const sub = record.observable.subscribe(next => callback(next));
30
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
31
+ const sub = store.observable.subscribe(callback);
27
32
  return () => {
28
33
  sub.unsubscribe();
29
34
  };
30
35
  },
31
36
  ];
32
37
  }, [observable]);
33
- useIsomorphicEffect(() => {
38
+ const shouldRestoreSubscriptionRef = useRef(false);
39
+ useEffect(() => {
40
+ const store = getOrCreateStore(observable, getValue(initialValue));
41
+ if (shouldRestoreSubscriptionRef.current) {
42
+ if (store.subscription.closed) {
43
+ store.subscription = store.observable.subscribe();
44
+ }
45
+ shouldRestoreSubscriptionRef.current = false;
46
+ }
34
47
  return () => {
35
- getOrCreateStore(observable, getValue(initialValue)).subscription.unsubscribe();
48
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
49
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
50
+ shouldRestoreSubscriptionRef.current = !store.subscription.closed;
51
+ store.subscription.unsubscribe();
36
52
  };
37
53
  }, [observable]);
38
54
  return useSyncExternalStore(subscribe, getSnapshot);
@@ -1,6 +1,5 @@
1
1
  import { BehaviorSubject } from 'rxjs';
2
- import { useCallback, useRef } from 'react';
3
- import { useIsomorphicEffect } from './useIsomorphicEffect';
2
+ import { useCallback, useEffect, useRef } from 'react';
4
3
  import { distinctUntilChanged } from 'rxjs/operators';
5
4
  export function useAsObservable(value, operator) {
6
5
  var setup = useCallback(function () {
@@ -13,18 +12,28 @@ export function useAsObservable(value, operator) {
13
12
  ref.current = setup();
14
13
  }
15
14
  var observable = ref.current[0];
16
- useIsomorphicEffect(function () {
15
+ useEffect(function () {
17
16
  if (!ref.current) {
18
17
  return;
19
18
  }
20
19
  var _a = ref.current, subject = _a[1];
21
20
  subject.next(value);
22
21
  }, [value, ref]);
23
- useIsomorphicEffect(function () {
22
+ var shouldRestoreSubscriptionRef = useRef(false);
23
+ useEffect(function () {
24
+ if (shouldRestoreSubscriptionRef.current) {
25
+ if (!ref.current) {
26
+ ref.current = setup();
27
+ }
28
+ shouldRestoreSubscriptionRef.current = false;
29
+ }
24
30
  return function () {
25
31
  if (!ref.current) {
26
32
  return;
27
33
  }
34
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
35
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
36
+ shouldRestoreSubscriptionRef.current = true;
28
37
  var _a = ref.current, subject = _a[1];
29
38
  subject.complete();
30
39
  ref.current = undefined;
@@ -1,2 +1,2 @@
1
- import { useLayoutEffect } from 'react';
2
- export declare const useIsomorphicEffect: typeof useLayoutEffect;
1
+ import { useEffect } from 'react';
2
+ export declare const useIsomorphicEffect: typeof useEffect;
@@ -1,7 +1,6 @@
1
- import { useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
  import { useSyncExternalStore } from 'use-sync-external-store/shim';
3
3
  import { shareReplay, tap } from 'rxjs/operators';
4
- import { useIsomorphicEffect } from './useIsomorphicEffect';
5
4
  function getValue(value) {
6
5
  return typeof value === 'function' ? value() : value;
7
6
  }
@@ -10,6 +9,7 @@ function getOrCreateStore(inputObservable, initialValue) {
10
9
  if (!cache.has(inputObservable)) {
11
10
  var entry_1 = { currentValue: initialValue };
12
11
  entry_1.observable = inputObservable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap(function (value) { return (entry_1.currentValue = value); }));
12
+ // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
13
13
  entry_1.subscription = entry_1.observable.subscribe();
14
14
  cache.set(inputObservable, entry_1);
15
15
  }
@@ -17,22 +17,38 @@ function getOrCreateStore(inputObservable, initialValue) {
17
17
  }
18
18
  export function useObservable(observable, initialValue) {
19
19
  var _a = useMemo(function () {
20
- var record = getOrCreateStore(observable, getValue(initialValue));
20
+ var store = getOrCreateStore(observable, getValue(initialValue));
21
+ if (store.subscription.closed) {
22
+ store.subscription = store.observable.subscribe();
23
+ }
21
24
  return [
22
25
  function getSnapshot() {
23
- return record.currentValue;
26
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here to clear up some memory, as this subscription is only needed to provide a sync initialValue.
27
+ return store.currentValue;
24
28
  },
25
29
  function subscribe(callback) {
26
- var sub = record.observable.subscribe(function (next) { return callback(next); });
30
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
31
+ var sub = store.observable.subscribe(callback);
27
32
  return function () {
28
33
  sub.unsubscribe();
29
34
  };
30
35
  },
31
36
  ];
32
37
  }, [observable]), getSnapshot = _a[0], subscribe = _a[1];
33
- useIsomorphicEffect(function () {
38
+ var shouldRestoreSubscriptionRef = useRef(false);
39
+ useEffect(function () {
40
+ var store = getOrCreateStore(observable, getValue(initialValue));
41
+ if (shouldRestoreSubscriptionRef.current) {
42
+ if (store.subscription.closed) {
43
+ store.subscription = store.observable.subscribe();
44
+ }
45
+ shouldRestoreSubscriptionRef.current = false;
46
+ }
34
47
  return function () {
35
- getOrCreateStore(observable, getValue(initialValue)).subscription.unsubscribe();
48
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
49
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
50
+ shouldRestoreSubscriptionRef.current = !store.subscription.closed;
51
+ store.subscription.unsubscribe();
36
52
  };
37
53
  }, [observable]);
38
54
  return useSyncExternalStore(subscribe, getSnapshot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-rx",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "React + RxJS = <3",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/cjs/index.d.ts",
@@ -1,6 +1,5 @@
1
1
  import {BehaviorSubject, Observable} from 'rxjs'
2
- import {useCallback, useRef} from 'react'
3
- import {useIsomorphicEffect} from './useIsomorphicEffect'
2
+ import {useCallback, useEffect, useRef} from 'react'
4
3
  import {distinctUntilChanged} from 'rxjs/operators'
5
4
 
6
5
  /**
@@ -33,7 +32,7 @@ export function useAsObservable<T, K = T>(
33
32
 
34
33
  const [observable] = ref.current
35
34
 
36
- useIsomorphicEffect(() => {
35
+ useEffect(() => {
37
36
  if (!ref.current) {
38
37
  return
39
38
  }
@@ -41,11 +40,22 @@ export function useAsObservable<T, K = T>(
41
40
  subject.next(value)
42
41
  }, [value, ref])
43
42
 
44
- useIsomorphicEffect(() => {
43
+ const shouldRestoreSubscriptionRef = useRef(false)
44
+ useEffect(() => {
45
+ if (shouldRestoreSubscriptionRef.current) {
46
+ if (!ref.current) {
47
+ ref.current = setup()
48
+ }
49
+ shouldRestoreSubscriptionRef.current = false
50
+ }
51
+
45
52
  return () => {
46
53
  if (!ref.current) {
47
54
  return
48
55
  }
56
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
57
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
58
+ shouldRestoreSubscriptionRef.current = true
49
59
  const [, subject] = ref.current
50
60
  subject.complete()
51
61
  ref.current = undefined
@@ -1,8 +1,7 @@
1
1
  import {Observable, Subscription} from 'rxjs'
2
- import {DependencyList, useMemo} from 'react'
2
+ import {DependencyList, useEffect, useMemo, useRef} from 'react'
3
3
  import {useSyncExternalStore} from 'use-sync-external-store/shim'
4
4
  import {shareReplay, tap} from 'rxjs/operators'
5
- import {useIsomorphicEffect} from './useIsomorphicEffect'
6
5
 
7
6
  function getValue<T>(value: T): T extends () => infer U ? U : T {
8
7
  return typeof value === 'function' ? value() : value
@@ -22,25 +21,34 @@ function getOrCreateStore<T>(inputObservable: Observable<T>, initialValue: T) {
22
21
  shareReplay({refCount: true, bufferSize: 1}),
23
22
  tap(value => (entry.currentValue = value)),
24
23
  )
24
+
25
+ // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
25
26
  entry.subscription = entry.observable.subscribe()
26
27
 
27
28
  cache.set(inputObservable, entry as CacheRecord<T>)
28
29
  }
29
- return cache.get(inputObservable)
30
+ return cache.get(inputObservable)!
30
31
  }
31
32
 
32
33
  export function useObservable<T>(observable: Observable<T>): T | undefined
33
34
  export function useObservable<T>(observable: Observable<T>, initialValue: T): T
34
35
  export function useObservable<T>(observable: Observable<T>, initialValue: () => T): T
35
36
  export function useObservable<T>(observable: Observable<T>, initialValue?: T | (() => T)) {
36
- const [getSnapshot, subscribe] = useMemo(() => {
37
- const record = getOrCreateStore(observable, getValue(initialValue))!
37
+ const [getSnapshot, subscribe] = useMemo<
38
+ [() => T, Parameters<typeof useSyncExternalStore>[0]]
39
+ >(() => {
40
+ const store = getOrCreateStore(observable, getValue(initialValue))
41
+ if (store.subscription.closed) {
42
+ store.subscription = store.observable.subscribe()
43
+ }
38
44
  return [
39
45
  function getSnapshot() {
40
- return record.currentValue
46
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here to clear up some memory, as this subscription is only needed to provide a sync initialValue.
47
+ return store.currentValue
41
48
  },
42
- function subscribe(callback: (value: T) => void) {
43
- const sub = record.observable.subscribe(next => callback(next))
49
+ function subscribe(callback: () => void) {
50
+ // @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
51
+ const sub = store.observable.subscribe(callback)
44
52
  return () => {
45
53
  sub.unsubscribe()
46
54
  }
@@ -48,9 +56,21 @@ export function useObservable<T>(observable: Observable<T>, initialValue?: T | (
48
56
  ]
49
57
  }, [observable])
50
58
 
51
- useIsomorphicEffect(() => {
59
+ const shouldRestoreSubscriptionRef = useRef(false)
60
+ useEffect(() => {
61
+ const store = getOrCreateStore(observable, getValue(initialValue))
62
+ if (shouldRestoreSubscriptionRef.current) {
63
+ if (store.subscription.closed) {
64
+ store.subscription = store.observable.subscribe()
65
+ }
66
+ shouldRestoreSubscriptionRef.current = false
67
+ }
68
+
52
69
  return () => {
53
- getOrCreateStore(observable, getValue(initialValue))!.subscription.unsubscribe()
70
+ // React StrictMode will call effects as `setup + teardown + setup` thus we can't trust this callback as "react is about to unmount"
71
+ // Tracking this ref lets us set the subscription back up on the next `setup` call if needed, and if it really did unmounted then all is well
72
+ shouldRestoreSubscriptionRef.current = !store.subscription.closed
73
+ store.subscription.unsubscribe()
54
74
  }
55
75
  }, [observable])
56
76