@@ -11,14 +11,17 @@ import {
1111 distinctUntilChanged ,
1212 EMPTY ,
1313 identity ,
14+ map ,
1415 type Observable ,
1516 type Subscription ,
1617 shareReplay ,
1718 startWith ,
1819 tap ,
1920} from "rxjs"
21+ import { filterObjectByKey } from "../utils/filterObjectByKey"
2022import { makeObservable } from "../utils/makeObservable"
2123import { useLiveRef } from "../utils/react/useLiveRef"
24+ import { isShallowEqual } from "../utils/shallowEqual"
2225
2326interface Option < R = undefined > {
2427 defaultValue : R
@@ -27,8 +30,20 @@ interface Option<R = undefined> {
2730}
2831
2932export function useObserve < T > ( source : BehaviorSubject < T > ) : T
33+ export function useObserve < T extends object , SelectorKeys extends keyof T > (
34+ source : BehaviorSubject < T > ,
35+ selector : SelectorKeys [ ] ,
36+ ) : { [ K in SelectorKeys ] : T [ K ] }
37+ export function useObserve < T > (
38+ source : BehaviorSubject < T > ,
39+ options : Omit < Option < T > , "defaultValue" > ,
40+ ) : T
3041
3142export function useObserve < T > ( source : Observable < T > ) : T | undefined
43+ export function useObserve < T extends object , SelectorKeys extends keyof T > (
44+ source : Observable < T > ,
45+ selector : SelectorKeys [ ] ,
46+ ) : { [ K in SelectorKeys ] : T [ K ] } | undefined
3247
3348export function useObserve < T > (
3449 source : ( ) => Observable < T > ,
@@ -48,19 +63,19 @@ export function useObserve<T>(
4863 deps : DependencyList ,
4964) : T
5065
51- export function useObserve < T > (
66+ export function useObserve < T , SelectorKeys extends keyof T > (
5267 source$ : Observable < T > | ( ( ) => Observable < T > | undefined ) ,
53- optionsOrDeps ?: Option < T > | DependencyList ,
68+ optionsOrDeps ?: Partial < Option < T > > | DependencyList | SelectorKeys [ ] ,
5469 maybeDeps ?: DependencyList ,
5570) : T {
5671 const options =
5772 optionsOrDeps != null && ! Array . isArray ( optionsOrDeps )
58- ? ( optionsOrDeps as Option < T > )
73+ ? ( optionsOrDeps as Partial < Option < T > > )
5974 : ( {
6075 defaultValue : undefined ,
6176 unsubscribeOnUnmount : true ,
6277 compareFn : undefined ,
63- } satisfies Option < undefined > )
78+ } satisfies Partial < Option < T > > )
6479 const deps =
6580 ! maybeDeps && Array . isArray ( optionsOrDeps )
6681 ? optionsOrDeps
@@ -70,18 +85,54 @@ export function useObserve<T>(
7085 const valueRef = useRef < { value : T | undefined } | undefined > ( undefined )
7186 const sourceRef = useLiveRef ( source$ )
7287 const optionsRef = useLiveRef ( options )
88+ const selectorKey =
89+ typeof source$ !== "function" && Array . isArray ( optionsOrDeps )
90+ ? JSON . stringify ( optionsOrDeps )
91+ : undefined
92+ const selectorRef = useLiveRef (
93+ typeof source$ !== "function" && Array . isArray ( optionsOrDeps )
94+ ? ( optionsOrDeps as SelectorKeys [ ] )
95+ : undefined ,
96+ )
7397
74- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO
75- const observable = useMemo (
76- ( ) => ( {
77- observable : makeObservable ( sourceRef . current ) ( ) . pipe (
98+ const observable = useMemo ( ( ) => {
99+ void selectorKey
100+
101+ const selectorOption = selectorRef . current
102+ const compareFnOption = optionsRef . current . compareFn
103+ const compareFn = compareFnOption
104+ ? compareFnOption
105+ : selectorOption
106+ ? isShallowEqual
107+ : undefined
108+ const observable$ = makeObservable ( sourceRef . current ) ( )
109+
110+ return {
111+ observable : observable$ . pipe (
112+ // Maybe selector
113+ map ( ( v ) => {
114+ if ( selectorOption && typeof v === "object" && v !== null ) {
115+ return filterObjectByKey ( v , selectorOption ) as T | undefined
116+ }
117+
118+ return v
119+ } ) ,
120+ // Maybe compareFn
121+ distinctUntilChanged ( ( a , b ) => {
122+ if ( a === undefined || b === undefined ) return false
123+
124+ if ( compareFn ) {
125+ return compareFn ( a as T , b as T )
126+ }
127+
128+ return a === b
129+ } ) ,
78130 shareReplay ( { refCount : true , bufferSize : 1 } ) ,
79131 ) ,
80132 subscribed : false ,
81133 snapshotSub : undefined as Subscription | undefined ,
82- } ) ,
83- [ ...deps ] ,
84- )
134+ }
135+ } , [ ...deps , selectorKey , selectorRef , sourceRef , optionsRef ] )
85136
86137 const getSnapshot = useCallback ( ( ) => {
87138 /**
@@ -92,8 +143,8 @@ export function useObserve<T>(
92143 if ( ! observable . subscribed ) {
93144 observable . subscribed = true
94145
95- const sub = observable . observable . subscribe ( ( v ) => {
96- valueRef . current = { value : v }
146+ const sub = observable . observable . subscribe ( ( value ) => {
147+ valueRef . current = { value : value as T }
97148 } )
98149
99150 observable . snapshotSub = sub
@@ -114,21 +165,8 @@ export function useObserve<T>(
114165 optionsRef . current . defaultValue
115166 ? startWith ( optionsRef . current . defaultValue )
116167 : identity ,
117- /**
118- * @important there is already a Object.is comparison in place from react
119- * so we only add a custom compareFn if provided
120- */
121- distinctUntilChanged ( ( a , b ) => {
122- if ( optionsRef . current . compareFn ) {
123- if ( a === undefined || b === undefined ) return false
124-
125- return optionsRef . current . compareFn ( a , b )
126- }
127-
128- return false
129- } ) ,
130168 tap ( ( value ) => {
131- valueRef . current = { value }
169+ valueRef . current = { value : value as T }
132170 } ) ,
133171 catchError ( ( error ) => {
134172 console . error ( error )
0 commit comments