1- import { useEffect , useState } from 'react' ;
1+ import { useCallback , useMemo } from 'react' ;
22
33import type { StateStore } from 'stream-chat' ;
4+ import { useSyncExternalStore } from 'use-sync-external-store/shim' ;
5+
6+ const noop = ( ) => { } ;
47
58export function useStateStore <
69 T extends Record < string , unknown > ,
@@ -14,22 +17,48 @@ export function useStateStore<
1417 T extends Record < string , unknown > ,
1518 O extends Readonly < Record < string , unknown > | Readonly < unknown [ ] > > ,
1619> ( store : StateStore < T > | undefined , selector : ( v : T ) => O ) {
17- const [ state , setState ] = useState < O | undefined > ( ( ) => {
18- if ( ! store ) {
19- return undefined ;
20- }
21- return selector ( store . getLatestValue ( ) ) ;
22- } ) ;
20+ const wrappedSubscription = useCallback (
21+ ( onStoreChange : ( ) => void ) => {
22+ const unsubscribe = store ?. subscribeWithSelector ( selector , onStoreChange ) ;
23+ return unsubscribe ?? noop ;
24+ } ,
25+ [ store , selector ] ,
26+ ) ;
27+
28+ const wrappedSnapshot = useMemo ( ( ) => {
29+ let cachedTuple : [ T , O ] ;
30+
31+ return ( ) => {
32+ const currentValue = store ?. getLatestValue ( ) ;
33+
34+ if ( ! currentValue ) return undefined ;
2335
24- useEffect ( ( ) => {
25- if ( ! store ) {
26- return ;
27- }
36+ // store value hasn't changed, no need to compare individual values
37+ if ( cachedTuple && cachedTuple [ 0 ] === currentValue ) {
38+ return cachedTuple [ 1 ] ;
39+ }
2840
29- const unsubscribe = store . subscribeWithSelector ( selector , setState ) ;
41+ const newlySelected = selector ( currentValue ) ;
3042
31- return unsubscribe ;
43+ // store value changed but selected values wouldn't have to, double-check selected
44+ if ( cachedTuple ) {
45+ let selectededAreEqualToCached = true ;
46+
47+ for ( const key in cachedTuple [ 1 ] ) {
48+ if ( cachedTuple [ 1 ] [ key ] === newlySelected [ key ] ) continue ;
49+ selectededAreEqualToCached = false ;
50+ break ;
51+ }
52+
53+ if ( selectededAreEqualToCached ) return cachedTuple [ 1 ] ;
54+ }
55+
56+ cachedTuple = [ currentValue , newlySelected ] ;
57+ return cachedTuple [ 1 ] ;
58+ } ;
3259 } , [ store , selector ] ) ;
3360
61+ const state = useSyncExternalStore ( wrappedSubscription , wrappedSnapshot ) ;
62+
3463 return state ;
3564}
0 commit comments