react-rx 3.0.0-2 → 3.0.0-4
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/__tests__/strictmode.test.js +1 -1
- package/dist/cjs/useObservable.d.ts +2 -2
- package/dist/cjs/useObservable.js +21 -42
- package/dist/cjs/useObservableEvent.d.ts +5 -0
- package/dist/cjs/useObservableEvent.js +30 -1
- package/dist/es2015/__tests__/strictmode.test.js +1 -1
- package/dist/es2015/useObservable.d.ts +2 -2
- package/dist/es2015/useObservable.js +21 -42
- package/dist/es2015/useObservableEvent.d.ts +5 -0
- package/dist/es2015/useObservableEvent.js +24 -1
- package/dist/esm/__tests__/strictmode.test.js +1 -1
- package/dist/esm/useObservable.d.ts +2 -2
- package/dist/esm/useObservable.js +21 -42
- package/dist/esm/useObservableEvent.d.ts +5 -0
- package/dist/esm/useObservableEvent.js +28 -1
- package/package.json +15 -14
- package/src/__tests__/strictmode.test.ts +1 -1
- package/src/useObservable.ts +30 -55
- package/src/useObservableEvent.ts +36 -1
|
@@ -14,7 +14,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
14
14
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
15
|
function step(op) {
|
|
16
16
|
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (_) try {
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
18
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
19
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
20
|
switch (op[0]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Observable } from 'rxjs';
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
2
|
export declare function useObservable<ObservableType extends Observable<any>>(observable: ObservableType, initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>)): UnboxObservable<ObservableType>;
|
|
3
|
-
|
|
3
|
+
type UnboxObservable<T> = T extends Observable<infer U> ? U : never;
|
|
4
4
|
export {};
|
|
@@ -7,16 +7,6 @@ function getValue(value) {
|
|
|
7
7
|
return typeof value === 'function' ? value() : value;
|
|
8
8
|
}
|
|
9
9
|
var cache = new WeakMap();
|
|
10
|
-
function getOrCreateStore(inputObservable, initialValue) {
|
|
11
|
-
if (!cache.has(inputObservable)) {
|
|
12
|
-
var entry_1 = { currentValue: initialValue };
|
|
13
|
-
entry_1.observable = inputObservable.pipe((0, operators_1.shareReplay)({ refCount: true, bufferSize: 1 }), (0, operators_1.tap)(function (value) { return (entry_1.currentValue = value); }));
|
|
14
|
-
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
|
|
15
|
-
entry_1.subscription = entry_1.observable.subscribe();
|
|
16
|
-
cache.set(inputObservable, entry_1);
|
|
17
|
-
}
|
|
18
|
-
return cache.get(inputObservable);
|
|
19
|
-
}
|
|
20
10
|
function useObservable(observable, initialValue) {
|
|
21
11
|
/**
|
|
22
12
|
* Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.
|
|
@@ -29,41 +19,30 @@ function useObservable(observable, initialValue) {
|
|
|
29
19
|
(0, react_1.useEffect)(function () {
|
|
30
20
|
initialValueRef.current = getValue(initialValue);
|
|
31
21
|
}, [initialValue]);
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
var store = (0, react_1.useMemo)(function () {
|
|
23
|
+
if (!cache.has(observable)) {
|
|
24
|
+
var entry_1 = {
|
|
25
|
+
snapshot: initialValueRef.current,
|
|
26
|
+
};
|
|
27
|
+
entry_1.observable = observable.pipe((0, operators_1.shareReplay)({ refCount: true, bufferSize: 1 }), (0, operators_1.tap)(function (value) { return (entry_1.snapshot = value); }));
|
|
28
|
+
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
|
|
29
|
+
entry_1.subscription = entry_1.observable.subscribe();
|
|
30
|
+
cache.set(observable, entry_1);
|
|
36
31
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
function subscribe(callback) {
|
|
43
|
-
// @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
|
|
44
|
-
var sub = store.observable.subscribe(callback);
|
|
45
|
-
return function () {
|
|
46
|
-
sub.unsubscribe();
|
|
47
|
-
};
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
}, [observable]), getSnapshot = _a[0], subscribe = _a[1];
|
|
51
|
-
var shouldRestoreSubscriptionRef = (0, react_1.useRef)(false);
|
|
52
|
-
(0, react_1.useEffect)(function () {
|
|
53
|
-
var store = getOrCreateStore(observable, initialValueRef.current);
|
|
54
|
-
if (shouldRestoreSubscriptionRef.current) {
|
|
55
|
-
if (store.subscription.closed) {
|
|
56
|
-
store.subscription = store.observable.subscribe();
|
|
57
|
-
}
|
|
58
|
-
shouldRestoreSubscriptionRef.current = false;
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
33
|
+
var instance = cache.get(observable);
|
|
34
|
+
if (instance.subscription.closed) {
|
|
35
|
+
instance.subscription = instance.observable.subscribe();
|
|
59
36
|
}
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
37
|
+
return {
|
|
38
|
+
subscribe: function (onStoreChange) {
|
|
39
|
+
var subscription = instance.observable.subscribe(onStoreChange);
|
|
40
|
+
instance.subscription.unsubscribe();
|
|
41
|
+
return function () { return subscription.unsubscribe(); };
|
|
42
|
+
},
|
|
43
|
+
getSnapshot: function () { return instance.snapshot; },
|
|
65
44
|
};
|
|
66
45
|
}, [observable]);
|
|
67
|
-
return (0, react_1.useSyncExternalStore)(subscribe, getSnapshot);
|
|
46
|
+
return (0, react_1.useSyncExternalStore)(store.subscribe, store.getSnapshot);
|
|
68
47
|
}
|
|
69
48
|
exports.useObservable = useObservable;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { DependencyList } from 'react';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
export declare function useObservableCallback<T, U>(fn: (arg: Observable<T>) => Observable<U>, dependencies?: DependencyList): (arg: T) => void;
|
|
4
|
+
export declare function useObservableEvent<T, U>(fn: (arg: Observable<T>) => Observable<U>): (arg: T) => void;
|
|
5
|
+
/**
|
|
6
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
7
|
+
*/
|
|
8
|
+
export declare function useEffectEvent<const T extends (...args: any[]) => void>(fn: T): T;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useObservableCallback = void 0;
|
|
3
|
+
exports.useEffectEvent = exports.useObservableEvent = exports.useObservableCallback = void 0;
|
|
4
4
|
var observable_callback_1 = require("observable-callback");
|
|
5
5
|
var react_1 = require("react");
|
|
6
6
|
var EMPTY_DEPS = [];
|
|
@@ -21,3 +21,32 @@ function useObservableCallback(fn, dependencies) {
|
|
|
21
21
|
return call;
|
|
22
22
|
}
|
|
23
23
|
exports.useObservableCallback = useObservableCallback;
|
|
24
|
+
function useObservableEvent(fn) {
|
|
25
|
+
var _a = (0, react_1.useState)(function () { return (0, observable_callback_1.observableCallback)(); })[0], calls$ = _a[0], call = _a[1];
|
|
26
|
+
var callback = useEffectEvent(fn);
|
|
27
|
+
(0, react_1.useEffect)(function () {
|
|
28
|
+
var subscription = calls$.pipe(callback).subscribe();
|
|
29
|
+
return function () { return subscription.unsubscribe(); };
|
|
30
|
+
}, [callback, calls$]);
|
|
31
|
+
return call;
|
|
32
|
+
}
|
|
33
|
+
exports.useObservableEvent = useObservableEvent;
|
|
34
|
+
/**
|
|
35
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
36
|
+
*/
|
|
37
|
+
function useEffectEvent(fn) {
|
|
38
|
+
var ref = (0, react_1.useRef)(null);
|
|
39
|
+
(0, react_1.useInsertionEffect)(function () {
|
|
40
|
+
ref.current = fn;
|
|
41
|
+
}, [fn]);
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
return (0, react_1.useCallback)(function () {
|
|
44
|
+
var args = [];
|
|
45
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
46
|
+
args[_i] = arguments[_i];
|
|
47
|
+
}
|
|
48
|
+
var f = ref.current;
|
|
49
|
+
return f.apply(void 0, args);
|
|
50
|
+
}, []);
|
|
51
|
+
}
|
|
52
|
+
exports.useEffectEvent = useEffectEvent;
|
|
@@ -11,7 +11,7 @@ import { act, render } from '@testing-library/react';
|
|
|
11
11
|
import { createElement, Fragment, StrictMode, useEffect } from 'react';
|
|
12
12
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
13
13
|
import { useObservable } from '../useObservable';
|
|
14
|
-
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
15
|
// NOTE: Jest runs NODE_ENV=test by default, which enables development flags for React
|
|
16
16
|
test('Strict mode should trigger double mount effects and re-renders', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
17
|
const subject = new BehaviorSubject(0);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Observable } from 'rxjs';
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
2
|
export declare function useObservable<ObservableType extends Observable<any>>(observable: ObservableType, initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>)): UnboxObservable<ObservableType>;
|
|
3
|
-
|
|
3
|
+
type UnboxObservable<T> = T extends Observable<infer U> ? U : never;
|
|
4
4
|
export {};
|
|
@@ -4,16 +4,6 @@ function getValue(value) {
|
|
|
4
4
|
return typeof value === 'function' ? value() : value;
|
|
5
5
|
}
|
|
6
6
|
const cache = new WeakMap();
|
|
7
|
-
function getOrCreateStore(inputObservable, initialValue) {
|
|
8
|
-
if (!cache.has(inputObservable)) {
|
|
9
|
-
const entry = { currentValue: initialValue };
|
|
10
|
-
entry.observable = inputObservable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap(value => (entry.currentValue = value)));
|
|
11
|
-
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
|
|
12
|
-
entry.subscription = entry.observable.subscribe();
|
|
13
|
-
cache.set(inputObservable, entry);
|
|
14
|
-
}
|
|
15
|
-
return cache.get(inputObservable);
|
|
16
|
-
}
|
|
17
7
|
export function useObservable(observable, initialValue) {
|
|
18
8
|
/**
|
|
19
9
|
* Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.
|
|
@@ -26,40 +16,29 @@ export function useObservable(observable, initialValue) {
|
|
|
26
16
|
useEffect(() => {
|
|
27
17
|
initialValueRef.current = getValue(initialValue);
|
|
28
18
|
}, [initialValue]);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
const store = useMemo(() => {
|
|
20
|
+
if (!cache.has(observable)) {
|
|
21
|
+
const entry = {
|
|
22
|
+
snapshot: initialValueRef.current,
|
|
23
|
+
};
|
|
24
|
+
entry.observable = observable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap((value) => (entry.snapshot = value)));
|
|
25
|
+
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
|
|
26
|
+
entry.subscription = entry.observable.subscribe();
|
|
27
|
+
cache.set(observable, entry);
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
function subscribe(callback) {
|
|
40
|
-
// @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
|
|
41
|
-
const sub = store.observable.subscribe(callback);
|
|
42
|
-
return () => {
|
|
43
|
-
sub.unsubscribe();
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
}, [observable]);
|
|
48
|
-
const shouldRestoreSubscriptionRef = useRef(false);
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const store = getOrCreateStore(observable, initialValueRef.current);
|
|
51
|
-
if (shouldRestoreSubscriptionRef.current) {
|
|
52
|
-
if (store.subscription.closed) {
|
|
53
|
-
store.subscription = store.observable.subscribe();
|
|
54
|
-
}
|
|
55
|
-
shouldRestoreSubscriptionRef.current = false;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
30
|
+
const instance = cache.get(observable);
|
|
31
|
+
if (instance.subscription.closed) {
|
|
32
|
+
instance.subscription = instance.observable.subscribe();
|
|
56
33
|
}
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
return {
|
|
35
|
+
subscribe: (onStoreChange) => {
|
|
36
|
+
const subscription = instance.observable.subscribe(onStoreChange);
|
|
37
|
+
instance.subscription.unsubscribe();
|
|
38
|
+
return () => subscription.unsubscribe();
|
|
39
|
+
},
|
|
40
|
+
getSnapshot: () => instance.snapshot,
|
|
62
41
|
};
|
|
63
42
|
}, [observable]);
|
|
64
|
-
return useSyncExternalStore(subscribe, getSnapshot);
|
|
43
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
65
44
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { DependencyList } from 'react';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
export declare function useObservableCallback<T, U>(fn: (arg: Observable<T>) => Observable<U>, dependencies?: DependencyList): (arg: T) => void;
|
|
4
|
+
export declare function useObservableEvent<T, U>(fn: (arg: Observable<T>) => Observable<U>): (arg: T) => void;
|
|
5
|
+
/**
|
|
6
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
7
|
+
*/
|
|
8
|
+
export declare function useEffectEvent<const T extends (...args: any[]) => void>(fn: T): T;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { observableCallback } from 'observable-callback';
|
|
2
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';
|
|
3
3
|
const EMPTY_DEPS = [];
|
|
4
4
|
export function useObservableCallback(fn, dependencies = EMPTY_DEPS) {
|
|
5
5
|
const callbackRef = useRef();
|
|
@@ -16,3 +16,26 @@ export function useObservableCallback(fn, dependencies = EMPTY_DEPS) {
|
|
|
16
16
|
}, [calls$, call, callback]);
|
|
17
17
|
return call;
|
|
18
18
|
}
|
|
19
|
+
export function useObservableEvent(fn) {
|
|
20
|
+
const [[calls$, call]] = useState(() => observableCallback());
|
|
21
|
+
const callback = useEffectEvent(fn);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const subscription = calls$.pipe(callback).subscribe();
|
|
24
|
+
return () => subscription.unsubscribe();
|
|
25
|
+
}, [callback, calls$]);
|
|
26
|
+
return call;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
30
|
+
*/
|
|
31
|
+
export function useEffectEvent(fn) {
|
|
32
|
+
const ref = useRef(null);
|
|
33
|
+
useInsertionEffect(() => {
|
|
34
|
+
ref.current = fn;
|
|
35
|
+
}, [fn]);
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
return useCallback((...args) => {
|
|
38
|
+
const f = ref.current;
|
|
39
|
+
return f(...args);
|
|
40
|
+
}, []);
|
|
41
|
+
}
|
|
@@ -13,7 +13,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
13
13
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
14
|
function step(op) {
|
|
15
15
|
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
-
while (_) try {
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
17
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
18
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
19
|
switch (op[0]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Observable } from 'rxjs';
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
2
|
export declare function useObservable<ObservableType extends Observable<any>>(observable: ObservableType, initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>)): UnboxObservable<ObservableType>;
|
|
3
|
-
|
|
3
|
+
type UnboxObservable<T> = T extends Observable<infer U> ? U : never;
|
|
4
4
|
export {};
|
|
@@ -4,16 +4,6 @@ function getValue(value) {
|
|
|
4
4
|
return typeof value === 'function' ? value() : value;
|
|
5
5
|
}
|
|
6
6
|
var cache = new WeakMap();
|
|
7
|
-
function getOrCreateStore(inputObservable, initialValue) {
|
|
8
|
-
if (!cache.has(inputObservable)) {
|
|
9
|
-
var entry_1 = { currentValue: initialValue };
|
|
10
|
-
entry_1.observable = inputObservable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap(function (value) { return (entry_1.currentValue = value); }));
|
|
11
|
-
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
|
|
12
|
-
entry_1.subscription = entry_1.observable.subscribe();
|
|
13
|
-
cache.set(inputObservable, entry_1);
|
|
14
|
-
}
|
|
15
|
-
return cache.get(inputObservable);
|
|
16
|
-
}
|
|
17
7
|
export function useObservable(observable, initialValue) {
|
|
18
8
|
/**
|
|
19
9
|
* Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.
|
|
@@ -26,40 +16,29 @@ export function useObservable(observable, initialValue) {
|
|
|
26
16
|
useEffect(function () {
|
|
27
17
|
initialValueRef.current = getValue(initialValue);
|
|
28
18
|
}, [initialValue]);
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
var store = useMemo(function () {
|
|
20
|
+
if (!cache.has(observable)) {
|
|
21
|
+
var entry_1 = {
|
|
22
|
+
snapshot: initialValueRef.current,
|
|
23
|
+
};
|
|
24
|
+
entry_1.observable = observable.pipe(shareReplay({ refCount: true, bufferSize: 1 }), tap(function (value) { return (entry_1.snapshot = value); }));
|
|
25
|
+
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
|
|
26
|
+
entry_1.subscription = entry_1.observable.subscribe();
|
|
27
|
+
cache.set(observable, entry_1);
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
function subscribe(callback) {
|
|
40
|
-
// @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
|
|
41
|
-
var sub = store.observable.subscribe(callback);
|
|
42
|
-
return function () {
|
|
43
|
-
sub.unsubscribe();
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
}, [observable]), getSnapshot = _a[0], subscribe = _a[1];
|
|
48
|
-
var shouldRestoreSubscriptionRef = useRef(false);
|
|
49
|
-
useEffect(function () {
|
|
50
|
-
var store = getOrCreateStore(observable, initialValueRef.current);
|
|
51
|
-
if (shouldRestoreSubscriptionRef.current) {
|
|
52
|
-
if (store.subscription.closed) {
|
|
53
|
-
store.subscription = store.observable.subscribe();
|
|
54
|
-
}
|
|
55
|
-
shouldRestoreSubscriptionRef.current = false;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
30
|
+
var instance = cache.get(observable);
|
|
31
|
+
if (instance.subscription.closed) {
|
|
32
|
+
instance.subscription = instance.observable.subscribe();
|
|
56
33
|
}
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
return {
|
|
35
|
+
subscribe: function (onStoreChange) {
|
|
36
|
+
var subscription = instance.observable.subscribe(onStoreChange);
|
|
37
|
+
instance.subscription.unsubscribe();
|
|
38
|
+
return function () { return subscription.unsubscribe(); };
|
|
39
|
+
},
|
|
40
|
+
getSnapshot: function () { return instance.snapshot; },
|
|
62
41
|
};
|
|
63
42
|
}, [observable]);
|
|
64
|
-
return useSyncExternalStore(subscribe, getSnapshot);
|
|
43
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
65
44
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { DependencyList } from 'react';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
export declare function useObservableCallback<T, U>(fn: (arg: Observable<T>) => Observable<U>, dependencies?: DependencyList): (arg: T) => void;
|
|
4
|
+
export declare function useObservableEvent<T, U>(fn: (arg: Observable<T>) => Observable<U>): (arg: T) => void;
|
|
5
|
+
/**
|
|
6
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
7
|
+
*/
|
|
8
|
+
export declare function useEffectEvent<const T extends (...args: any[]) => void>(fn: T): T;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { observableCallback } from 'observable-callback';
|
|
2
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';
|
|
3
3
|
var EMPTY_DEPS = [];
|
|
4
4
|
export function useObservableCallback(fn, dependencies) {
|
|
5
5
|
if (dependencies === void 0) { dependencies = EMPTY_DEPS; }
|
|
@@ -17,3 +17,30 @@ export function useObservableCallback(fn, dependencies) {
|
|
|
17
17
|
}, [calls$, call, callback]);
|
|
18
18
|
return call;
|
|
19
19
|
}
|
|
20
|
+
export function useObservableEvent(fn) {
|
|
21
|
+
var _a = useState(function () { return observableCallback(); })[0], calls$ = _a[0], call = _a[1];
|
|
22
|
+
var callback = useEffectEvent(fn);
|
|
23
|
+
useEffect(function () {
|
|
24
|
+
var subscription = calls$.pipe(callback).subscribe();
|
|
25
|
+
return function () { return subscription.unsubscribe(); };
|
|
26
|
+
}, [callback, calls$]);
|
|
27
|
+
return call;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
31
|
+
*/
|
|
32
|
+
export function useEffectEvent(fn) {
|
|
33
|
+
var ref = useRef(null);
|
|
34
|
+
useInsertionEffect(function () {
|
|
35
|
+
ref.current = fn;
|
|
36
|
+
}, [fn]);
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
return useCallback(function () {
|
|
39
|
+
var args = [];
|
|
40
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
41
|
+
args[_i] = arguments[_i];
|
|
42
|
+
}
|
|
43
|
+
var f = ref.current;
|
|
44
|
+
return f.apply(void 0, args);
|
|
45
|
+
}, []);
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-rx",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-4",
|
|
4
4
|
"description": "React + RxJS = <3",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"action",
|
|
@@ -69,39 +69,40 @@
|
|
|
69
69
|
"test": "jest",
|
|
70
70
|
"lint": "eslint --cache ."
|
|
71
71
|
},
|
|
72
|
+
"prettier": "@sanity/prettier-config",
|
|
72
73
|
"dependencies": {
|
|
73
74
|
"observable-callback": "^1.0.3"
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
77
|
+
"@sanity/prettier-config": "^1.0.2",
|
|
76
78
|
"@sanity/semantic-release-preset": "^2.0.5",
|
|
77
|
-
"@testing-library/dom": "^
|
|
78
|
-
"@testing-library/react": "^
|
|
79
|
+
"@testing-library/dom": "^10.1.0",
|
|
80
|
+
"@testing-library/react": "^16.0.0",
|
|
79
81
|
"@types/jest": "^29.5.3",
|
|
80
82
|
"@types/node": "^18.17.5",
|
|
81
83
|
"@types/react": "^18.3.3",
|
|
82
84
|
"@types/react-dom": "^18.3.0",
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "7.12.0",
|
|
84
|
-
"@typescript-eslint/parser": "7.12.0",
|
|
85
|
-
"eslint": "8.57.0",
|
|
86
|
-
"eslint-config-prettier": "9.1.0",
|
|
87
|
-
"eslint-plugin-prettier": "5.1.3",
|
|
88
|
-
"eslint-plugin-react": "7.34.2",
|
|
89
|
-
"eslint-plugin-react-compiler": "
|
|
90
|
-
"eslint-plugin-react-hooks": "4.6.2",
|
|
85
|
+
"@typescript-eslint/eslint-plugin": "^7.12.0",
|
|
86
|
+
"@typescript-eslint/parser": "^7.12.0",
|
|
87
|
+
"eslint": "^8.57.0",
|
|
88
|
+
"eslint-config-prettier": "^9.1.0",
|
|
89
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
90
|
+
"eslint-plugin-react": "^7.34.2",
|
|
91
|
+
"eslint-plugin-react-compiler": "0.0.0-experimental-51a85ea-20240601",
|
|
92
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
91
93
|
"eslint-plugin-simple-import-sort": "^12.1.0",
|
|
92
94
|
"jest": "^29.6.2",
|
|
93
95
|
"jest-environment-jsdom": "^29.6.2",
|
|
94
96
|
"jsdom": "^20.0.3",
|
|
95
97
|
"npm-run-all": "^4.1.5",
|
|
96
|
-
"prettier": "^
|
|
97
|
-
"prettier-plugin-packagejson": "^2.4.5",
|
|
98
|
+
"prettier": "^3.3.1",
|
|
98
99
|
"react": "^18.3.1",
|
|
99
100
|
"react-dom": "^18.3.1",
|
|
100
101
|
"react-test-renderer": "^18.3.1",
|
|
101
102
|
"rimraf": "^3.0.2",
|
|
102
103
|
"rxjs": "^7.8.1",
|
|
103
104
|
"ts-jest": "^29.1.1",
|
|
104
|
-
"typescript": "4.
|
|
105
|
+
"typescript": "5.4.5"
|
|
105
106
|
},
|
|
106
107
|
"peerDependencies": {
|
|
107
108
|
"react": "^18.3 || >=19.0.0-rc",
|
|
@@ -4,7 +4,7 @@ import {BehaviorSubject, Observable} from 'rxjs'
|
|
|
4
4
|
|
|
5
5
|
import {useObservable} from '../useObservable'
|
|
6
6
|
|
|
7
|
-
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
7
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
8
8
|
|
|
9
9
|
// NOTE: Jest runs NODE_ENV=test by default, which enables development flags for React
|
|
10
10
|
|
package/src/useObservable.ts
CHANGED
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'
|
|
2
|
-
import {Observable, Subscription} from 'rxjs'
|
|
2
|
+
import type {Observable, Subscription} from 'rxjs'
|
|
3
3
|
import {shareReplay, tap} from 'rxjs/operators'
|
|
4
4
|
|
|
5
5
|
function getValue<T>(value: T): T extends () => infer U ? U : T {
|
|
6
6
|
return typeof value === 'function' ? value() : value
|
|
7
7
|
}
|
|
8
|
-
|
|
9
8
|
interface CacheRecord<T> {
|
|
10
9
|
subscription: Subscription
|
|
11
10
|
observable: Observable<T>
|
|
12
|
-
|
|
11
|
+
snapshot: T
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
const cache = new WeakMap<Observable<any>, CacheRecord<any>>()
|
|
16
|
-
function getOrCreateStore<T>(inputObservable: Observable<T>, initialValue: T) {
|
|
17
|
-
if (!cache.has(inputObservable)) {
|
|
18
|
-
const entry: Partial<CacheRecord<T>> = {currentValue: initialValue}
|
|
19
|
-
entry.observable = inputObservable.pipe(
|
|
20
|
-
shareReplay({refCount: true, bufferSize: 1}),
|
|
21
|
-
tap(value => (entry.currentValue = value)),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns
|
|
25
|
-
entry.subscription = entry.observable.subscribe()
|
|
26
|
-
|
|
27
|
-
cache.set(inputObservable, entry as CacheRecord<T>)
|
|
28
|
-
}
|
|
29
|
-
return cache.get(inputObservable)!
|
|
30
|
-
}
|
|
31
15
|
|
|
32
16
|
export function useObservable<ObservableType extends Observable<any>>(
|
|
33
17
|
observable: ObservableType,
|
|
@@ -37,56 +21,47 @@ export function useObservable<ObservableType extends Observable<any>>(
|
|
|
37
21
|
* Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.
|
|
38
22
|
* But we also don't want the initialValue to be stale if the observable changes.
|
|
39
23
|
*/
|
|
40
|
-
const initialValueRef = useRef(getValue(initialValue))
|
|
24
|
+
const initialValueRef = useRef(getValue(initialValue) as UnboxObservable<ObservableType>)
|
|
41
25
|
|
|
42
26
|
/**
|
|
43
27
|
* Ensures that the initialValue is always up-to-date in case the observable changes.
|
|
44
28
|
*/
|
|
45
29
|
useEffect(() => {
|
|
46
|
-
initialValueRef.current = getValue(initialValue)
|
|
30
|
+
initialValueRef.current = getValue(initialValue) as UnboxObservable<ObservableType>
|
|
47
31
|
}, [initialValue])
|
|
48
32
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (store.subscription.closed) {
|
|
54
|
-
store.subscription = store.observable.subscribe()
|
|
55
|
-
}
|
|
56
|
-
return [
|
|
57
|
-
function getSnapshot() {
|
|
58
|
-
// @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.
|
|
59
|
-
return store.currentValue
|
|
60
|
-
},
|
|
61
|
-
function subscribe(callback: () => void) {
|
|
62
|
-
// @TODO: perf opt opportunity: we could do `store.subscription.unsubscribe()` here as we only need 1 subscription active to keep the observer alive
|
|
63
|
-
const sub = store.observable.subscribe(callback)
|
|
64
|
-
return () => {
|
|
65
|
-
sub.unsubscribe()
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
]
|
|
69
|
-
}, [observable])
|
|
70
|
-
|
|
71
|
-
const shouldRestoreSubscriptionRef = useRef(false)
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const store = getOrCreateStore(observable, initialValueRef.current)
|
|
74
|
-
if (shouldRestoreSubscriptionRef.current) {
|
|
75
|
-
if (store.subscription.closed) {
|
|
76
|
-
store.subscription = store.observable.subscribe()
|
|
33
|
+
const store = useMemo(() => {
|
|
34
|
+
if (!cache.has(observable)) {
|
|
35
|
+
const entry: Partial<CacheRecord<UnboxObservable<ObservableType>>> = {
|
|
36
|
+
snapshot: initialValueRef.current,
|
|
77
37
|
}
|
|
78
|
-
|
|
38
|
+
entry.observable = observable.pipe(
|
|
39
|
+
shareReplay({refCount: true, bufferSize: 1}),
|
|
40
|
+
tap((value) => (entry.snapshot = value)),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
|
|
44
|
+
entry.subscription = entry.observable.subscribe()
|
|
45
|
+
|
|
46
|
+
cache.set(observable, entry as CacheRecord<UnboxObservable<ObservableType>>)
|
|
47
|
+
}
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
49
|
+
const instance = cache.get(observable)!
|
|
50
|
+
if (instance.subscription.closed) {
|
|
51
|
+
instance.subscription = instance.observable.subscribe()
|
|
79
52
|
}
|
|
80
53
|
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
54
|
+
return {
|
|
55
|
+
subscribe: (onStoreChange: () => void) => {
|
|
56
|
+
const subscription = instance.observable.subscribe(onStoreChange)
|
|
57
|
+
instance.subscription.unsubscribe()
|
|
58
|
+
return () => subscription.unsubscribe()
|
|
59
|
+
},
|
|
60
|
+
getSnapshot: () => instance.snapshot,
|
|
86
61
|
}
|
|
87
62
|
}, [observable])
|
|
88
63
|
|
|
89
|
-
return useSyncExternalStore<UnboxObservable<ObservableType>>(subscribe, getSnapshot)
|
|
64
|
+
return useSyncExternalStore<UnboxObservable<ObservableType>>(store.subscribe, store.getSnapshot)
|
|
90
65
|
}
|
|
91
66
|
|
|
92
67
|
type UnboxObservable<T> = T extends Observable<infer U> ? U : never
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {observableCallback} from 'observable-callback'
|
|
2
|
-
import {DependencyList, useCallback, useEffect, useRef} from 'react'
|
|
2
|
+
import {DependencyList, useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'
|
|
3
3
|
import {Observable} from 'rxjs'
|
|
4
4
|
|
|
5
5
|
const EMPTY_DEPS: DependencyList = []
|
|
@@ -27,3 +27,38 @@ export function useObservableCallback<T, U>(
|
|
|
27
27
|
|
|
28
28
|
return call
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export function useObservableEvent<T, U>(
|
|
32
|
+
fn: (arg: Observable<T>) => Observable<U>,
|
|
33
|
+
): (arg: T) => void {
|
|
34
|
+
const [[calls$, call]] = useState(() => observableCallback<T>())
|
|
35
|
+
|
|
36
|
+
const callback = useEffectEvent(fn)
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const subscription = calls$.pipe(callback).subscribe()
|
|
40
|
+
return () => subscription.unsubscribe()
|
|
41
|
+
}, [callback, calls$])
|
|
42
|
+
|
|
43
|
+
return call
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
|
|
48
|
+
*/
|
|
49
|
+
export function useEffectEvent<
|
|
50
|
+
const T extends (
|
|
51
|
+
...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
any[]
|
|
53
|
+
) => void,
|
|
54
|
+
>(fn: T): T {
|
|
55
|
+
const ref = useRef<T | null>(null)
|
|
56
|
+
useInsertionEffect(() => {
|
|
57
|
+
ref.current = fn
|
|
58
|
+
}, [fn])
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
return useCallback((...args: any[]) => {
|
|
61
|
+
const f = ref.current!
|
|
62
|
+
return f(...args)
|
|
63
|
+
}, []) as T
|
|
64
|
+
}
|