Skip to content

Commit ad4b147

Browse files
feat(react-bindings): integrate useSyncExternalStore in useObservableValue (#1953)
### 💡 Overview ### 📝 Implementation notes 🎫 Ticket: https://linear.app/stream/issue/XYZ-123 📑 Docs: https://github.com/GetStream/docs-content/pull/<id>
1 parent 9213b27 commit ad4b147

File tree

4 files changed

+46
-15
lines changed

4 files changed

+46
-15
lines changed

packages/react-bindings/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
],
2323
"dependencies": {
2424
"i18next": "^25.6.0",
25-
"rxjs": "~7.8.2"
25+
"rxjs": "~7.8.2",
26+
"use-sync-external-store": "^1.6.0"
2627
},
2728
"peerDependencies": {
2829
"@stream-io/video-client": "workspace:^",
@@ -32,6 +33,7 @@
3233
"@rollup/plugin-typescript": "^12.1.4",
3334
"@stream-io/video-client": "workspace:^",
3435
"@types/react": "~19.1.17",
36+
"@types/use-sync-external-store": "^1",
3537
"react": "19.1.0",
3638
"rimraf": "^6.0.1",
3739
"rollup": "^4.52.4",

packages/react-bindings/src/hooks/callStateHooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ function useLazyDeviceList(manager: DeviceManagerLike) {
525525
setDevices$(manager.listDevices());
526526
}
527527

528-
return devices;
528+
return devices ?? EMPTY_DEVICES_ARRAY;
529529
};
530530

531531
return { getDevices };
Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
11
import type { Observable } from 'rxjs';
2-
import { useEffect, useState } from 'react';
2+
import { useCallback } from 'react';
3+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
34
import { RxUtils } from '@stream-io/video-client';
45

56
/**
67
* Utility hook which provides the current value of the given observable.
78
*
89
* @param observable$ the observable to read data from.
9-
* @param defaultValue a default value. Used when the observable data can't be read or emits an error.
10+
* @param defaultValue a default value. Used when the observable data can't be read or emits an error - must be stable.
1011
*/
1112
export const useObservableValue = <T>(
1213
observable$: Observable<T>,
1314
defaultValue?: T,
1415
) => {
15-
const [value, setValue] = useState<T>(() => {
16+
const getSnapshot = useCallback(() => {
1617
try {
1718
return RxUtils.getCurrentValue(observable$);
18-
} catch (err) {
19-
if (typeof defaultValue === 'undefined') throw err;
19+
} catch (error) {
20+
if (typeof defaultValue === 'undefined') throw error;
2021
return defaultValue;
2122
}
22-
});
23-
24-
useEffect(() => {
25-
return RxUtils.createSubscription(observable$, setValue, (err) => {
26-
console.log('An error occurred while reading an observable', err);
27-
if (defaultValue) setValue(defaultValue);
28-
});
2923
}, [defaultValue, observable$]);
3024

31-
return value;
25+
const subscribe = useCallback(
26+
(onStoreChange: (v: T) => void) => {
27+
const unsubscribe = RxUtils.createSubscription(
28+
observable$,
29+
onStoreChange,
30+
(error) => {
31+
console.log('An error occurred while reading an observable', error);
32+
33+
if (defaultValue) onStoreChange(defaultValue);
34+
},
35+
);
36+
37+
return unsubscribe;
38+
},
39+
[defaultValue, observable$],
40+
);
41+
42+
return useSyncExternalStore(subscribe, getSnapshot);
3243
};

yarn.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7406,12 +7406,14 @@ __metadata:
74067406
"@rollup/plugin-typescript": "npm:^12.1.4"
74077407
"@stream-io/video-client": "workspace:^"
74087408
"@types/react": "npm:~19.1.17"
7409+
"@types/use-sync-external-store": "npm:^1"
74097410
i18next: "npm:^25.6.0"
74107411
react: "npm:19.1.0"
74117412
rimraf: "npm:^6.0.1"
74127413
rollup: "npm:^4.52.4"
74137414
rxjs: "npm:~7.8.2"
74147415
typescript: "npm:^5.9.3"
7416+
use-sync-external-store: "npm:^1.6.0"
74157417
peerDependencies:
74167418
"@stream-io/video-client": "workspace:^"
74177419
react: ^17 || ^18 || ^19
@@ -8268,6 +8270,13 @@ __metadata:
82688270
languageName: node
82698271
linkType: hard
82708272

8273+
"@types/use-sync-external-store@npm:^1":
8274+
version: 1.5.0
8275+
resolution: "@types/use-sync-external-store@npm:1.5.0"
8276+
checksum: 10/39e5be8dc2cca080b490f2f79fed4381ae7eebee3f981208e359856733eafb2479d229db07a552f6c99fe0b5c09b3e46a3e6a870e00a88b50f3e690e73d2649b
8277+
languageName: node
8278+
linkType: hard
8279+
82718280
"@types/whatwg-mimetype@npm:^3.0.2":
82728281
version: 3.0.2
82738282
resolution: "@types/whatwg-mimetype@npm:3.0.2"
@@ -23891,6 +23900,15 @@ __metadata:
2389123900
languageName: node
2389223901
linkType: hard
2389323902

23903+
"use-sync-external-store@npm:^1.6.0":
23904+
version: 1.6.0
23905+
resolution: "use-sync-external-store@npm:1.6.0"
23906+
peerDependencies:
23907+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
23908+
checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e
23909+
languageName: node
23910+
linkType: hard
23911+
2389423912
"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
2389523913
version: 1.0.2
2389623914
resolution: "util-deprecate@npm:1.0.2"

0 commit comments

Comments
 (0)