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.
- package/dist/cjs/useAsObservable.js +12 -3
- package/dist/cjs/useIsomorphicEffect.d.ts +2 -2
- package/dist/cjs/useObservable.js +22 -6
- package/dist/es2015/useAsObservable.js +13 -4
- package/dist/es2015/useIsomorphicEffect.d.ts +2 -2
- package/dist/es2015/useObservable.js +23 -7
- package/dist/esm/useAsObservable.js +13 -4
- package/dist/esm/useIsomorphicEffect.d.ts +2 -2
- package/dist/esm/useObservable.js +23 -7
- package/package.json +1 -1
- package/src/useAsObservable.ts +14 -4
- package/src/useObservable.ts +30 -10
|
@@ -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,
|
|
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,
|
|
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 {
|
|
2
|
-
export declare const useIsomorphicEffect: typeof
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
export declare const useIsomorphicEffect: typeof
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
export declare const useIsomorphicEffect: typeof
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/src/useAsObservable.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
package/src/useObservable.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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: (
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|