react-rx 3.1.3 → 4.0.1-canary.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/index.cjs +67 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +69 -49
- package/dist/index.js.map +1 -1
- package/package.json +28 -28
- package/src/__tests__/strictmode.test.ts +28 -4
- package/src/__tests__/useObservable.test-d.ts +1 -1
- package/src/__tests__/useObservable.test.ts +12 -5
- package/src/useObservable.ts +34 -48
package/dist/index.cjs
CHANGED
|
@@ -1,62 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
-
var react = require("react"), rxjs = require("rxjs"), operators = require("rxjs/operators"), observableCallback = require("observable-callback"), useEffectEvent = require("use-effect-event");
|
|
3
|
+
var reactCompilerRuntime = require("react-compiler-runtime"), react = require("react"), rxjs = require("rxjs"), operators = require("rxjs/operators"), observableCallback = require("observable-callback"), useEffectEvent = require("use-effect-event");
|
|
4
4
|
function getValue(value) {
|
|
5
5
|
return typeof value == "function" ? value() : value;
|
|
6
6
|
}
|
|
7
7
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
8
8
|
function useObservable(observable, initialValue) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const store = react.useMemo(() => {
|
|
14
|
-
if (!cache.has(observable)) {
|
|
15
|
-
const entry = {
|
|
16
|
-
snapshot: initialValueRef.current
|
|
17
|
-
};
|
|
18
|
-
entry.observable = observable.pipe(
|
|
19
|
-
operators.map((value) => ({ snapshot: value, error: void 0 })),
|
|
20
|
-
rxjs.catchError((error) => rxjs.of({ snapshot: void 0, error })),
|
|
21
|
-
operators.tap(({ snapshot, error }) => {
|
|
22
|
-
entry.snapshot = snapshot, entry.error = error;
|
|
23
|
-
}),
|
|
24
|
-
// Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
|
|
25
|
-
// and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
|
|
26
|
-
operators.map((value) => {
|
|
27
|
-
}),
|
|
28
|
-
// Ensure that the cache entry is deleted when the observable completes or errors.
|
|
29
|
-
rxjs.finalize(() => cache.delete(observable)),
|
|
30
|
-
rxjs.share()
|
|
31
|
-
), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
|
|
32
|
-
}
|
|
33
|
-
const instance = cache.get(observable);
|
|
34
|
-
return instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), {
|
|
35
|
-
subscribe: (onStoreChange) => {
|
|
36
|
-
const subscription = instance.observable.subscribe(onStoreChange);
|
|
37
|
-
return instance.subscription.unsubscribe(), () => {
|
|
38
|
-
subscription.unsubscribe();
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
getSnapshot: () => {
|
|
42
|
-
if (instance.error)
|
|
43
|
-
throw instance.error;
|
|
44
|
-
return instance.snapshot;
|
|
45
|
-
}
|
|
9
|
+
const $ = reactCompilerRuntime.c(12);
|
|
10
|
+
if (!cache.has(observable)) {
|
|
11
|
+
const entry = {
|
|
12
|
+
snapshot: getValue(initialValue)
|
|
46
13
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
14
|
+
entry.observable = observable.pipe(operators.map(_temp$1), rxjs.catchError(_temp2), operators.tap((t02) => {
|
|
15
|
+
const {
|
|
16
|
+
snapshot,
|
|
17
|
+
error: error_0
|
|
18
|
+
} = t02;
|
|
19
|
+
entry.snapshot = snapshot, entry.error = error_0;
|
|
20
|
+
}), operators.map(_temp3), rxjs.finalize(() => cache.delete(observable)), rxjs.share({
|
|
21
|
+
resetOnRefCountZero: _temp4
|
|
22
|
+
})), entry.observable.subscribe().unsubscribe(), cache.set(observable, entry);
|
|
23
|
+
}
|
|
24
|
+
let t0;
|
|
25
|
+
$[0] !== observable ? (t0 = cache.get(observable), $[0] = observable, $[1] = t0) : t0 = $[1];
|
|
26
|
+
const instance = t0;
|
|
27
|
+
let t1, t2;
|
|
28
|
+
$[2] !== instance.observable ? (t2 = (onStoreChange) => {
|
|
29
|
+
const subscription_0 = instance.observable.subscribe(onStoreChange);
|
|
30
|
+
return () => {
|
|
31
|
+
subscription_0.unsubscribe();
|
|
32
|
+
};
|
|
33
|
+
}, $[2] = instance.observable, $[3] = t2) : t2 = $[3];
|
|
34
|
+
let t3;
|
|
35
|
+
$[4] !== instance.error || $[5] !== instance.snapshot ? (t3 = () => {
|
|
36
|
+
if (instance.error)
|
|
37
|
+
throw instance.error;
|
|
38
|
+
return instance.snapshot;
|
|
39
|
+
}, $[4] = instance.error, $[5] = instance.snapshot, $[6] = t3) : t3 = $[6];
|
|
40
|
+
let t4;
|
|
41
|
+
$[7] !== t2 || $[8] !== t3 ? (t4 = {
|
|
42
|
+
subscribe: t2,
|
|
43
|
+
getSnapshot: t3
|
|
44
|
+
}, $[7] = t2, $[8] = t3, $[9] = t4) : t4 = $[9], t1 = t4;
|
|
45
|
+
const store = t1;
|
|
46
|
+
let t5;
|
|
47
|
+
return $[10] !== initialValue ? (t5 = typeof initialValue > "u" ? void 0 : () => getValue(initialValue), $[10] = initialValue, $[11] = t5) : t5 = $[11], react.useSyncExternalStore(store.subscribe, store.getSnapshot, t5);
|
|
48
|
+
}
|
|
49
|
+
function _temp4() {
|
|
50
|
+
return rxjs.timer(0, rxjs.asapScheduler);
|
|
51
|
+
}
|
|
52
|
+
function _temp3(value_0) {
|
|
53
|
+
}
|
|
54
|
+
function _temp2(error) {
|
|
55
|
+
return rxjs.of({
|
|
56
|
+
snapshot: void 0,
|
|
57
|
+
error
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function _temp$1(value) {
|
|
61
|
+
return {
|
|
62
|
+
snapshot: value,
|
|
63
|
+
error: void 0
|
|
64
|
+
};
|
|
53
65
|
}
|
|
54
66
|
function useObservableEvent(handleEvent) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
const $ = reactCompilerRuntime.c(6), [t0] = react.useState(_temp), [calls$, call] = t0;
|
|
68
|
+
let t1;
|
|
69
|
+
$[0] !== handleEvent ? (t1 = (observable) => handleEvent(observable), $[0] = handleEvent, $[1] = t1) : t1 = $[1];
|
|
70
|
+
const onEvent = useEffectEvent.useEffectEvent(t1);
|
|
71
|
+
let t2, t3;
|
|
72
|
+
return $[2] !== calls$ || $[3] !== onEvent ? (t2 = () => {
|
|
73
|
+
const subscription = calls$.pipe((observable_0) => onEvent(observable_0)).subscribe();
|
|
58
74
|
return () => subscription.unsubscribe();
|
|
59
|
-
}, [calls$, onEvent]), call;
|
|
75
|
+
}, t3 = [calls$, onEvent], $[2] = calls$, $[3] = onEvent, $[4] = t2, $[5] = t3) : (t2 = $[4], t3 = $[5]), react.useEffect(t2, t3), call;
|
|
76
|
+
}
|
|
77
|
+
function _temp() {
|
|
78
|
+
return observableCallback.observableCallback();
|
|
60
79
|
}
|
|
61
80
|
exports.useObservable = useObservable;
|
|
62
81
|
exports.useObservableEvent = useObservableEvent;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useMemo, useSyncExternalStore} from 'react'\nimport {\n asapScheduler,\n catchError,\n finalize,\n type Observable,\n type ObservedValueOf,\n of,\n share,\n timer,\n} from 'rxjs'\nimport {map, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\n\ninterface CacheRecord<T> {\n observable: Observable<void>\n snapshot: T\n error?: unknown\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),\n): ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n): undefined | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue?: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType> {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {\n snapshot: getValue(initialValue) as ObservedValueOf<ObservableType>,\n }\n entry.observable = observable.pipe(\n map((value) => ({snapshot: value, error: undefined})),\n catchError((error) => of({snapshot: undefined, error})),\n tap(({snapshot, error}) => {\n entry.snapshot = snapshot\n entry.error = error\n }),\n // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state\n // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.\n map((value) => void value),\n // Ensure that the cache entry is deleted when the observable completes or errors.\n finalize(() => cache.delete(observable)),\n share({resetOnRefCountZero: () => timer(0, asapScheduler)}),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n const subscription = entry.observable.subscribe()\n subscription.unsubscribe()\n\n cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n\n const store = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n return () => {\n subscription.unsubscribe()\n }\n },\n getSnapshot: () => {\n if (instance.error) {\n throw instance.error\n }\n return instance.snapshot\n },\n }\n }, [instance])\n\n return useSyncExternalStore<ObservedValueOf<ObservableType>>(\n store.subscribe,\n store.getSnapshot,\n typeof initialValue === 'undefined'\n ? undefined\n : () => getValue(initialValue) as ObservedValueOf<ObservableType>,\n )\n}\n","import {observableCallback} from 'observable-callback'\nimport {useEffect, useState} from 'react'\nimport {type Observable} from 'rxjs'\nimport {useEffectEvent} from 'use-effect-event'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n handleEvent: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const onEvent = useEffectEvent((observable: Observable<T>) => handleEvent(observable))\n\n useEffect(() => {\n const subscription = calls$.pipe((observable) => onEvent(observable)).subscribe()\n return () => subscription.unsubscribe()\n }, [calls$, onEvent])\n\n return call\n}\n"],"names":["getValue","value","cache","WeakMap","useObservable","observable","initialValue","$","_c","has","entry","snapshot","pipe","map","_temp","catchError","_temp2","tap","t0","error","error_0","_temp3","finalize","delete","share","resetOnRefCountZero","_temp4","subscribe","unsubscribe","set","get","instance","t1","t2","onStoreChange","subscription_0","subscription","t3","t4","getSnapshot","store","t5","undefined","useSyncExternalStore","timer","asapScheduler","value_0","of","useObservableEvent","handleEvent","useState","calls$","call","onEvent","useEffectEvent","observable_0","useEffect","observableCallback"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function useObservable<ObservableType extends Observable<any>>(
|
|
|
15
15
|
/** @public */
|
|
16
16
|
export declare function useObservable<ObservableType extends Observable<any>, InitialValue>(
|
|
17
17
|
observable: ObservableType,
|
|
18
|
-
initialValue: InitialValue,
|
|
18
|
+
initialValue: InitialValue | (() => InitialValue),
|
|
19
19
|
): InitialValue | ObservedValueOf<ObservableType>
|
|
20
20
|
|
|
21
21
|
/** @public */
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function useObservable<ObservableType extends Observable<any>>(
|
|
|
15
15
|
/** @public */
|
|
16
16
|
export declare function useObservable<ObservableType extends Observable<any>, InitialValue>(
|
|
17
17
|
observable: ObservableType,
|
|
18
|
-
initialValue: InitialValue,
|
|
18
|
+
initialValue: InitialValue | (() => InitialValue),
|
|
19
19
|
): InitialValue | ObservedValueOf<ObservableType>
|
|
20
20
|
|
|
21
21
|
/** @public */
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { c } from "react-compiler-runtime";
|
|
2
|
+
import { useSyncExternalStore, useState, useEffect } from "react";
|
|
3
|
+
import { catchError, finalize, share, timer, asapScheduler, of } from "rxjs";
|
|
3
4
|
import { map, tap } from "rxjs/operators";
|
|
4
5
|
import { observableCallback } from "observable-callback";
|
|
5
6
|
import { useEffectEvent } from "use-effect-event";
|
|
@@ -8,57 +9,76 @@ function getValue(value) {
|
|
|
8
9
|
}
|
|
9
10
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
10
11
|
function useObservable(observable, initialValue) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const store = useMemo(() => {
|
|
16
|
-
if (!cache.has(observable)) {
|
|
17
|
-
const entry = {
|
|
18
|
-
snapshot: initialValueRef.current
|
|
19
|
-
};
|
|
20
|
-
entry.observable = observable.pipe(
|
|
21
|
-
map((value) => ({ snapshot: value, error: void 0 })),
|
|
22
|
-
catchError((error) => of({ snapshot: void 0, error })),
|
|
23
|
-
tap(({ snapshot, error }) => {
|
|
24
|
-
entry.snapshot = snapshot, entry.error = error;
|
|
25
|
-
}),
|
|
26
|
-
// Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
|
|
27
|
-
// and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
|
|
28
|
-
map((value) => {
|
|
29
|
-
}),
|
|
30
|
-
// Ensure that the cache entry is deleted when the observable completes or errors.
|
|
31
|
-
finalize(() => cache.delete(observable)),
|
|
32
|
-
share()
|
|
33
|
-
), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
|
|
34
|
-
}
|
|
35
|
-
const instance = cache.get(observable);
|
|
36
|
-
return instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), {
|
|
37
|
-
subscribe: (onStoreChange) => {
|
|
38
|
-
const subscription = instance.observable.subscribe(onStoreChange);
|
|
39
|
-
return instance.subscription.unsubscribe(), () => {
|
|
40
|
-
subscription.unsubscribe();
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
getSnapshot: () => {
|
|
44
|
-
if (instance.error)
|
|
45
|
-
throw instance.error;
|
|
46
|
-
return instance.snapshot;
|
|
47
|
-
}
|
|
12
|
+
const $ = c(12);
|
|
13
|
+
if (!cache.has(observable)) {
|
|
14
|
+
const entry = {
|
|
15
|
+
snapshot: getValue(initialValue)
|
|
48
16
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
entry.observable = observable.pipe(map(_temp$1), catchError(_temp2), tap((t02) => {
|
|
18
|
+
const {
|
|
19
|
+
snapshot,
|
|
20
|
+
error: error_0
|
|
21
|
+
} = t02;
|
|
22
|
+
entry.snapshot = snapshot, entry.error = error_0;
|
|
23
|
+
}), map(_temp3), finalize(() => cache.delete(observable)), share({
|
|
24
|
+
resetOnRefCountZero: _temp4
|
|
25
|
+
})), entry.observable.subscribe().unsubscribe(), cache.set(observable, entry);
|
|
26
|
+
}
|
|
27
|
+
let t0;
|
|
28
|
+
$[0] !== observable ? (t0 = cache.get(observable), $[0] = observable, $[1] = t0) : t0 = $[1];
|
|
29
|
+
const instance = t0;
|
|
30
|
+
let t1, t2;
|
|
31
|
+
$[2] !== instance.observable ? (t2 = (onStoreChange) => {
|
|
32
|
+
const subscription_0 = instance.observable.subscribe(onStoreChange);
|
|
33
|
+
return () => {
|
|
34
|
+
subscription_0.unsubscribe();
|
|
35
|
+
};
|
|
36
|
+
}, $[2] = instance.observable, $[3] = t2) : t2 = $[3];
|
|
37
|
+
let t3;
|
|
38
|
+
$[4] !== instance.error || $[5] !== instance.snapshot ? (t3 = () => {
|
|
39
|
+
if (instance.error)
|
|
40
|
+
throw instance.error;
|
|
41
|
+
return instance.snapshot;
|
|
42
|
+
}, $[4] = instance.error, $[5] = instance.snapshot, $[6] = t3) : t3 = $[6];
|
|
43
|
+
let t4;
|
|
44
|
+
$[7] !== t2 || $[8] !== t3 ? (t4 = {
|
|
45
|
+
subscribe: t2,
|
|
46
|
+
getSnapshot: t3
|
|
47
|
+
}, $[7] = t2, $[8] = t3, $[9] = t4) : t4 = $[9], t1 = t4;
|
|
48
|
+
const store = t1;
|
|
49
|
+
let t5;
|
|
50
|
+
return $[10] !== initialValue ? (t5 = typeof initialValue > "u" ? void 0 : () => getValue(initialValue), $[10] = initialValue, $[11] = t5) : t5 = $[11], useSyncExternalStore(store.subscribe, store.getSnapshot, t5);
|
|
51
|
+
}
|
|
52
|
+
function _temp4() {
|
|
53
|
+
return timer(0, asapScheduler);
|
|
54
|
+
}
|
|
55
|
+
function _temp3(value_0) {
|
|
56
|
+
}
|
|
57
|
+
function _temp2(error) {
|
|
58
|
+
return of({
|
|
59
|
+
snapshot: void 0,
|
|
60
|
+
error
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function _temp$1(value) {
|
|
64
|
+
return {
|
|
65
|
+
snapshot: value,
|
|
66
|
+
error: void 0
|
|
67
|
+
};
|
|
55
68
|
}
|
|
56
69
|
function useObservableEvent(handleEvent) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
const $ = c(6), [t0] = useState(_temp), [calls$, call] = t0;
|
|
71
|
+
let t1;
|
|
72
|
+
$[0] !== handleEvent ? (t1 = (observable) => handleEvent(observable), $[0] = handleEvent, $[1] = t1) : t1 = $[1];
|
|
73
|
+
const onEvent = useEffectEvent(t1);
|
|
74
|
+
let t2, t3;
|
|
75
|
+
return $[2] !== calls$ || $[3] !== onEvent ? (t2 = () => {
|
|
76
|
+
const subscription = calls$.pipe((observable_0) => onEvent(observable_0)).subscribe();
|
|
60
77
|
return () => subscription.unsubscribe();
|
|
61
|
-
}, [calls$, onEvent]), call;
|
|
78
|
+
}, t3 = [calls$, onEvent], $[2] = calls$, $[3] = onEvent, $[4] = t2, $[5] = t3) : (t2 = $[4], t3 = $[5]), useEffect(t2, t3), call;
|
|
79
|
+
}
|
|
80
|
+
function _temp() {
|
|
81
|
+
return observableCallback();
|
|
62
82
|
}
|
|
63
83
|
export {
|
|
64
84
|
useObservable,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useMemo, useSyncExternalStore} from 'react'\nimport {\n asapScheduler,\n catchError,\n finalize,\n type Observable,\n type ObservedValueOf,\n of,\n share,\n timer,\n} from 'rxjs'\nimport {map, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\n\ninterface CacheRecord<T> {\n observable: Observable<void>\n snapshot: T\n error?: unknown\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),\n): ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n): undefined | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue?: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType> {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {\n snapshot: getValue(initialValue) as ObservedValueOf<ObservableType>,\n }\n entry.observable = observable.pipe(\n map((value) => ({snapshot: value, error: undefined})),\n catchError((error) => of({snapshot: undefined, error})),\n tap(({snapshot, error}) => {\n entry.snapshot = snapshot\n entry.error = error\n }),\n // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state\n // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.\n map((value) => void value),\n // Ensure that the cache entry is deleted when the observable completes or errors.\n finalize(() => cache.delete(observable)),\n share({resetOnRefCountZero: () => timer(0, asapScheduler)}),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n const subscription = entry.observable.subscribe()\n subscription.unsubscribe()\n\n cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n\n const store = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n return () => {\n subscription.unsubscribe()\n }\n },\n getSnapshot: () => {\n if (instance.error) {\n throw instance.error\n }\n return instance.snapshot\n },\n }\n }, [instance])\n\n return useSyncExternalStore<ObservedValueOf<ObservableType>>(\n store.subscribe,\n store.getSnapshot,\n typeof initialValue === 'undefined'\n ? undefined\n : () => getValue(initialValue) as ObservedValueOf<ObservableType>,\n )\n}\n","import {observableCallback} from 'observable-callback'\nimport {useEffect, useState} from 'react'\nimport {type Observable} from 'rxjs'\nimport {useEffectEvent} from 'use-effect-event'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n handleEvent: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const onEvent = useEffectEvent((observable: Observable<T>) => handleEvent(observable))\n\n useEffect(() => {\n const subscription = calls$.pipe((observable) => onEvent(observable)).subscribe()\n return () => subscription.unsubscribe()\n }, [calls$, onEvent])\n\n return call\n}\n"],"names":["getValue","value","cache","WeakMap","useObservable","observable","initialValue","$","_c","has","entry","snapshot","pipe","map","_temp","catchError","_temp2","tap","t0","error","error_0","_temp3","finalize","delete","share","resetOnRefCountZero","_temp4","subscribe","unsubscribe","set","get","instance","t1","t2","onStoreChange","subscription_0","subscription","t3","t4","getSnapshot","store","t5","undefined","useSyncExternalStore","timer","asapScheduler","value_0","of","useObservableEvent","handleEvent","useState","calls$","call","onEvent","useEffectEvent","observable_0","useEffect","observableCallback"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-rx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1-canary.0",
|
|
4
4
|
"description": "React + RxJS = <3",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"action",
|
|
@@ -62,52 +62,52 @@
|
|
|
62
62
|
"dist",
|
|
63
63
|
"src"
|
|
64
64
|
],
|
|
65
|
-
"scripts": {
|
|
66
|
-
"build": "pkg build --strict --clean --check",
|
|
67
|
-
"dev": "pnpm --filter 'react-rx-website' dev",
|
|
68
|
-
"format": "prettier --cache --write .",
|
|
69
|
-
"lint": "eslint --cache .",
|
|
70
|
-
"prepublishOnly": "pnpm build",
|
|
71
|
-
"test": "vitest run --typecheck",
|
|
72
|
-
"watch": "pnpm build -- --watch"
|
|
73
|
-
},
|
|
74
65
|
"browserslist": "extends @sanity/browserslist-config",
|
|
75
66
|
"prettier": "@sanity/prettier-config",
|
|
76
67
|
"dependencies": {
|
|
77
68
|
"observable-callback": "^1.0.3",
|
|
69
|
+
"react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
|
|
78
70
|
"use-effect-event": "^1.0.2"
|
|
79
71
|
},
|
|
80
72
|
"devDependencies": {
|
|
81
|
-
"@sanity/pkg-utils": "^6.
|
|
82
|
-
"@sanity/prettier-config": "^1.0.
|
|
73
|
+
"@sanity/pkg-utils": "^6.11.7",
|
|
74
|
+
"@sanity/prettier-config": "^1.0.3",
|
|
83
75
|
"@sanity/semantic-release-preset": "^5.0.0",
|
|
84
|
-
"@testing-library/dom": "^10.
|
|
85
|
-
"@testing-library/react": "^16.0.
|
|
76
|
+
"@testing-library/dom": "^10.4.0",
|
|
77
|
+
"@testing-library/react": "^16.0.1",
|
|
86
78
|
"@types/node": "^18.17.5",
|
|
87
|
-
"@types/react": "^18.3.
|
|
79
|
+
"@types/react": "^18.3.4",
|
|
88
80
|
"@types/react-dom": "^18.3.0",
|
|
89
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
90
|
-
"@typescript-eslint/parser": "^7.
|
|
91
|
-
"
|
|
81
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
82
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
83
|
+
"babel-plugin-react-compiler": "beta",
|
|
84
|
+
"eslint": "^8.57.1",
|
|
92
85
|
"eslint-config-prettier": "^9.1.0",
|
|
93
|
-
"eslint-plugin-prettier": "^5.1
|
|
94
|
-
"eslint-plugin-react": "^7.
|
|
95
|
-
"eslint-plugin-react-compiler": "
|
|
86
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
87
|
+
"eslint-plugin-react": "^7.37.2",
|
|
88
|
+
"eslint-plugin-react-compiler": "beta",
|
|
96
89
|
"eslint-plugin-react-hooks": "^4.6.2",
|
|
97
90
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
98
91
|
"jsdom": "^24.1.0",
|
|
99
|
-
"prettier": "^3.3.
|
|
92
|
+
"prettier": "^3.3.3",
|
|
100
93
|
"react": "^18.3.1",
|
|
101
94
|
"react-dom": "^18.3.1",
|
|
102
95
|
"react-test-renderer": "^18.3.1",
|
|
103
96
|
"rxjs": "^7.8.1",
|
|
104
|
-
"semantic-release": "^24.
|
|
105
|
-
"typescript": "5.
|
|
106
|
-
"vitest": "^2.0.
|
|
97
|
+
"semantic-release": "^24.1.0",
|
|
98
|
+
"typescript": "5.6.3",
|
|
99
|
+
"vitest": "^2.0.5"
|
|
107
100
|
},
|
|
108
101
|
"peerDependencies": {
|
|
109
|
-
"react": "^18.3 || >=19.0.0-
|
|
102
|
+
"react": "^18.3 || >=19.0.0-0",
|
|
110
103
|
"rxjs": "^7"
|
|
111
104
|
},
|
|
112
|
-
"
|
|
113
|
-
|
|
105
|
+
"scripts": {
|
|
106
|
+
"build": "pkg build --strict --clean --check",
|
|
107
|
+
"dev": "pnpm --filter 'react-rx-website' dev",
|
|
108
|
+
"format": "prettier --cache --write .",
|
|
109
|
+
"lint": "eslint --cache .",
|
|
110
|
+
"test": "vitest run --typecheck",
|
|
111
|
+
"watch": "pnpm build -- --watch"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {act, render} from '@testing-library/react'
|
|
2
|
-
import {createElement, Fragment, StrictMode, useEffect} from 'react'
|
|
2
|
+
import {createElement, Fragment, StrictMode, useEffect, useMemo} from 'react'
|
|
3
3
|
import {BehaviorSubject, Observable} from 'rxjs'
|
|
4
4
|
import {expect, test} from 'vitest'
|
|
5
5
|
|
|
@@ -39,7 +39,7 @@ test('Strict mode should trigger double mount effects and re-renders', async ()
|
|
|
39
39
|
expect(mountCount).toEqual(2)
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
test('Strict mode should unsubscribe the source observable on unmount', () => {
|
|
42
|
+
test('Strict mode should unsubscribe the source observable on unmount', async () => {
|
|
43
43
|
const subscribed: number[] = []
|
|
44
44
|
const unsubscribed: number[] = []
|
|
45
45
|
let nextId = 0
|
|
@@ -57,7 +57,31 @@ test('Strict mode should unsubscribe the source observable on unmount', () => {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const {rerender} = render(createElement(StrictMode, null, createElement(ObservableComponent)))
|
|
60
|
-
expect(subscribed).toEqual([0
|
|
60
|
+
expect(subscribed).toEqual([0])
|
|
61
61
|
rerender(createElement(StrictMode, null, createElement('div')))
|
|
62
|
-
|
|
62
|
+
await Promise.resolve()
|
|
63
|
+
expect(unsubscribed).toEqual([0])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('Strict mode should unsubscribe the source observable on unmount if its created in a useMemo', async () => {
|
|
67
|
+
let subscriberCount: number = 0
|
|
68
|
+
const getObservable = () =>
|
|
69
|
+
new Observable(() => {
|
|
70
|
+
subscriberCount++
|
|
71
|
+
return () => {
|
|
72
|
+
subscriberCount--
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
function ObservableComponent() {
|
|
77
|
+
const memoObservable = useMemo(() => getObservable(), [])
|
|
78
|
+
useObservable(memoObservable)
|
|
79
|
+
return createElement(Fragment, null)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const {rerender} = render(createElement(StrictMode, null, createElement(ObservableComponent)))
|
|
83
|
+
expect(subscriberCount, 'Subscriber count should be 2').toBe(2)
|
|
84
|
+
rerender(createElement(StrictMode, null, createElement('div')))
|
|
85
|
+
await Promise.resolve()
|
|
86
|
+
expect(subscriberCount, 'Subscriber count should be 0').toBe(0)
|
|
63
87
|
})
|
|
@@ -22,6 +22,6 @@ test('useObservable with initial value if a different type returns a union of th
|
|
|
22
22
|
const observable = of('foo')
|
|
23
23
|
|
|
24
24
|
expectTypeOf(useObservable(observable, 1)).toEqualTypeOf<string | number>()
|
|
25
|
-
|
|
25
|
+
expectTypeOf(useObservable(observable, () => 1)).toEqualTypeOf<string | number>()
|
|
26
26
|
expectTypeOf(useObservable(observable, 'foo')).toEqualTypeOf<string>()
|
|
27
27
|
})
|
|
@@ -6,7 +6,7 @@ import {expect, test} from 'vitest'
|
|
|
6
6
|
|
|
7
7
|
import {useObservable} from '../useObservable'
|
|
8
8
|
|
|
9
|
-
test('should subscribe immediately on component mount and unsubscribe on component unmount', () => {
|
|
9
|
+
test('should subscribe immediately on component mount and unsubscribe on component unmount', async () => {
|
|
10
10
|
let subscribed = false
|
|
11
11
|
const observable = new Observable(() => {
|
|
12
12
|
subscribed = true
|
|
@@ -21,10 +21,11 @@ test('should subscribe immediately on component mount and unsubscribe on compone
|
|
|
21
21
|
expect(subscribed).toBe(true)
|
|
22
22
|
|
|
23
23
|
unmount()
|
|
24
|
+
await Promise.resolve()
|
|
24
25
|
expect(subscribed).toBe(false)
|
|
25
26
|
})
|
|
26
27
|
|
|
27
|
-
test('should only subscribe once when given same observable on re-renders', () => {
|
|
28
|
+
test('should only subscribe once when given same observable on re-renders', async () => {
|
|
28
29
|
let subscriptionCount = 0
|
|
29
30
|
const observable = new Observable(() => {
|
|
30
31
|
subscriptionCount++
|
|
@@ -37,6 +38,7 @@ test('should only subscribe once when given same observable on re-renders', () =
|
|
|
37
38
|
rerender()
|
|
38
39
|
expect(subscriptionCount).toBe(1)
|
|
39
40
|
unmount()
|
|
41
|
+
await Promise.resolve()
|
|
40
42
|
|
|
41
43
|
renderHook(() => useObservable(observable))
|
|
42
44
|
expect(subscriptionCount).toBe(2)
|
|
@@ -103,7 +105,7 @@ test('should have passed initialValue as initial value from delayed observables'
|
|
|
103
105
|
unmount()
|
|
104
106
|
})
|
|
105
107
|
|
|
106
|
-
test('should rerender with initial value if component unmounts and then remounts', () => {
|
|
108
|
+
test('should rerender with initial value if component unmounts and then remounts', async () => {
|
|
107
109
|
const values$ = new Subject<string>()
|
|
108
110
|
const firstHook = renderHook(() => useObservable(values$, 'initial'))
|
|
109
111
|
|
|
@@ -113,13 +115,14 @@ test('should rerender with initial value if component unmounts and then remounts
|
|
|
113
115
|
expect(firstHook.result.current).toBe('something')
|
|
114
116
|
|
|
115
117
|
firstHook.unmount()
|
|
118
|
+
await Promise.resolve()
|
|
116
119
|
|
|
117
120
|
const nextHook = renderHook(() => useObservable(values$, 'initial2'))
|
|
118
121
|
|
|
119
122
|
expect(nextHook.result.current).toBe('initial2')
|
|
120
123
|
})
|
|
121
124
|
|
|
122
|
-
test('should share the observable between each concurrent subscribing hook', () => {
|
|
125
|
+
test('should share the observable between each concurrent subscribing hook', async () => {
|
|
123
126
|
let subscribeCount = 0
|
|
124
127
|
const observable = new Observable<number>((subscriber) => {
|
|
125
128
|
subscriber.next(subscribeCount++)
|
|
@@ -130,13 +133,14 @@ test('should share the observable between each concurrent subscribing hook', ()
|
|
|
130
133
|
expect(secondHook.result.current).toBe(0)
|
|
131
134
|
firstHook.unmount()
|
|
132
135
|
secondHook.unmount()
|
|
136
|
+
await Promise.resolve()
|
|
133
137
|
|
|
134
138
|
const thirdHook = renderHook(() => useObservable(observable))
|
|
135
139
|
expect(thirdHook.result.current).toBe(1)
|
|
136
140
|
thirdHook.unmount()
|
|
137
141
|
})
|
|
138
142
|
|
|
139
|
-
test('should restart any completed observable on mount', () => {
|
|
143
|
+
test('should restart any completed observable on mount', async () => {
|
|
140
144
|
let subscribeCount = 0
|
|
141
145
|
let unsubscribeCount = 0
|
|
142
146
|
|
|
@@ -178,12 +182,15 @@ test('should restart any completed observable on mount', () => {
|
|
|
178
182
|
expect(unsubscribeCount).toBe(1)
|
|
179
183
|
|
|
180
184
|
firstHook.unmount()
|
|
185
|
+
await Promise.resolve()
|
|
181
186
|
|
|
182
187
|
const secondHook = renderHook(() => useObservable(observable))
|
|
183
188
|
expect(secondHook.result.current).toBe(undefined)
|
|
184
189
|
expect(subscribeCount).toBe(2)
|
|
185
190
|
expect(unsubscribeCount).toBe(1)
|
|
186
191
|
secondHook.unmount()
|
|
192
|
+
await Promise.resolve()
|
|
193
|
+
|
|
187
194
|
expect(unsubscribeCount).toBe(2)
|
|
188
195
|
})
|
|
189
196
|
|
package/src/useObservable.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {useMemo, useSyncExternalStore} from 'react'
|
|
2
2
|
import {
|
|
3
|
+
asapScheduler,
|
|
3
4
|
catchError,
|
|
4
5
|
finalize,
|
|
5
6
|
type Observable,
|
|
6
7
|
type ObservedValueOf,
|
|
7
8
|
of,
|
|
8
9
|
share,
|
|
9
|
-
|
|
10
|
+
timer,
|
|
10
11
|
} from 'rxjs'
|
|
11
12
|
import {map, tap} from 'rxjs/operators'
|
|
12
13
|
|
|
@@ -15,7 +16,6 @@ function getValue<T>(value: T): T extends () => infer U ? U : T {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
interface CacheRecord<T> {
|
|
18
|
-
subscription: Subscription
|
|
19
19
|
observable: Observable<void>
|
|
20
20
|
snapshot: T
|
|
21
21
|
error?: unknown
|
|
@@ -35,61 +35,45 @@ export function useObservable<ObservableType extends Observable<any>>(
|
|
|
35
35
|
/** @public */
|
|
36
36
|
export function useObservable<ObservableType extends Observable<any>, InitialValue>(
|
|
37
37
|
observable: ObservableType,
|
|
38
|
-
initialValue: InitialValue,
|
|
38
|
+
initialValue: InitialValue | (() => InitialValue),
|
|
39
39
|
): InitialValue | ObservedValueOf<ObservableType>
|
|
40
40
|
/** @public */
|
|
41
41
|
export function useObservable<ObservableType extends Observable<any>, InitialValue>(
|
|
42
42
|
observable: ObservableType,
|
|
43
43
|
initialValue?: InitialValue | (() => InitialValue),
|
|
44
44
|
): InitialValue | ObservedValueOf<ObservableType> {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
if (!cache.has(observable)) {
|
|
46
|
+
const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {
|
|
47
|
+
snapshot: getValue(initialValue) as ObservedValueOf<ObservableType>,
|
|
48
|
+
}
|
|
49
|
+
entry.observable = observable.pipe(
|
|
50
|
+
map((value) => ({snapshot: value, error: undefined})),
|
|
51
|
+
catchError((error) => of({snapshot: undefined, error})),
|
|
52
|
+
tap(({snapshot, error}) => {
|
|
53
|
+
entry.snapshot = snapshot
|
|
54
|
+
entry.error = error
|
|
55
|
+
}),
|
|
56
|
+
// Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
|
|
57
|
+
// and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
|
|
58
|
+
map((value) => void value),
|
|
59
|
+
// Ensure that the cache entry is deleted when the observable completes or errors.
|
|
60
|
+
finalize(() => cache.delete(observable)),
|
|
61
|
+
share({resetOnRefCountZero: () => timer(0, asapScheduler)}),
|
|
62
|
+
)
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
snapshot: initialValueRef.current,
|
|
62
|
-
}
|
|
63
|
-
entry.observable = observable.pipe(
|
|
64
|
-
map((value) => ({snapshot: value, error: undefined})),
|
|
65
|
-
catchError((error) => of({snapshot: undefined, error})),
|
|
66
|
-
tap(({snapshot, error}) => {
|
|
67
|
-
entry.snapshot = snapshot
|
|
68
|
-
entry.error = error
|
|
69
|
-
}),
|
|
70
|
-
// Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
|
|
71
|
-
// and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
|
|
72
|
-
map((value) => void value),
|
|
73
|
-
// Ensure that the cache entry is deleted when the observable completes or errors.
|
|
74
|
-
finalize(() => cache.delete(observable)),
|
|
75
|
-
share(),
|
|
76
|
-
)
|
|
64
|
+
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
|
|
65
|
+
const subscription = entry.observable.subscribe()
|
|
66
|
+
subscription.unsubscribe()
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
84
|
-
const instance = cache.get(observable)!
|
|
85
|
-
if (instance.subscription.closed) {
|
|
86
|
-
instance.subscription = instance.observable.subscribe()
|
|
87
|
-
}
|
|
68
|
+
cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)
|
|
69
|
+
}
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
71
|
+
const instance = cache.get(observable)!
|
|
88
72
|
|
|
73
|
+
const store = useMemo(() => {
|
|
89
74
|
return {
|
|
90
75
|
subscribe: (onStoreChange: () => void) => {
|
|
91
76
|
const subscription = instance.observable.subscribe(onStoreChange)
|
|
92
|
-
instance.subscription.unsubscribe()
|
|
93
77
|
return () => {
|
|
94
78
|
subscription.unsubscribe()
|
|
95
79
|
}
|
|
@@ -101,11 +85,13 @@ export function useObservable<ObservableType extends Observable<any>, InitialVal
|
|
|
101
85
|
return instance.snapshot
|
|
102
86
|
},
|
|
103
87
|
}
|
|
104
|
-
}, [
|
|
88
|
+
}, [instance])
|
|
105
89
|
|
|
106
90
|
return useSyncExternalStore<ObservedValueOf<ObservableType>>(
|
|
107
91
|
store.subscribe,
|
|
108
92
|
store.getSnapshot,
|
|
109
|
-
typeof
|
|
93
|
+
typeof initialValue === 'undefined'
|
|
94
|
+
? undefined
|
|
95
|
+
: () => getValue(initialValue) as ObservedValueOf<ObservableType>,
|
|
110
96
|
)
|
|
111
97
|
}
|