@@ -39,45 +39,74 @@ type ReactTimeSyncSubscriptionEntry = Readonly<
39
39
}
40
40
> ;
41
41
42
+ // Need to wrap each value that we put in the selection cache, so that when we
43
+ // try to retrieve a value, it's easy to differentiate between a value being
44
+ // undefined because that's an explicit selection value, versus it being
45
+ // undefined because we forgot to set it in the cache
46
+ type SelectionCacheEntry = Readonly < { value : unknown } > ;
47
+
42
48
interface ReactTimeSyncApi {
43
49
subscribe : ( entry : ReactTimeSyncSubscriptionEntry ) => ( ) => void ;
44
50
getSelectionSnapshot : < T = unknown > ( id : string ) => T ;
45
51
}
46
52
47
53
class ReactTimeSync implements ReactTimeSyncApi {
48
54
readonly #timeSync: TimeSync ;
49
- readonly #resyncOnNewSubscription: boolean ;
50
- readonly #selectionCache: Map < string , unknown > ;
55
+ readonly #selectionCache: Map < string , SelectionCacheEntry > ;
51
56
52
57
constructor ( options : Partial < TimeSyncInitOptions > ) {
53
- const {
54
- resyncOnNewSubscription = defaultOptions . resyncOnNewSubscription ,
55
- initialDatetime = defaultOptions . initialDatetime ,
56
- createNewDatetime = defaultOptions . createNewDatetime ,
57
- setInterval = defaultOptions . setInterval ,
58
- clearInterval = defaultOptions . clearInterval ,
59
- } = options ;
60
-
58
+ this . #timeSync = new TimeSync ( options ) ;
61
59
this . #selectionCache = new Map ( ) ;
62
- this . #resyncOnNewSubscription = resyncOnNewSubscription ;
63
- this . #timeSync = new TimeSync ( {
64
- initialDatetime,
65
- createNewDatetime,
66
- setInterval,
67
- clearInterval,
68
- } ) ;
69
60
}
70
61
71
62
// All functions that are part of the public interface must be defined as
72
63
// arrow functions, so that they work properly with React
73
64
74
65
subscribe = ( entry : ReactTimeSyncSubscriptionEntry ) : ( ( ) => void ) => {
75
- this . #timeSync. subscribe ( entry ) ;
76
- return ( ) => this . #timeSync. unsubscribe ( entry . id ) ;
66
+ const { select, id, idealRefreshIntervalMs, onUpdate } = entry ;
67
+
68
+ // Make sure that we subscribe first, in case TimeSync is configured to
69
+ // invalidate the snapshot on a new subscription. Want to remove risk of
70
+ // stale data
71
+ const patchedEntry : SubscriptionEntry = {
72
+ id,
73
+ idealRefreshIntervalMs,
74
+ onUpdate : ( newDate ) => {
75
+ const prevSelection = this . getSelectionSnapshot ( id ) ;
76
+ const newSelection : unknown = select ?.( newDate ) ?? newDate ;
77
+ if ( newSelection === prevSelection ) {
78
+ return ;
79
+ }
80
+
81
+ this . #selectionCache. set ( id , { value : newSelection } ) ;
82
+ onUpdate ( newDate ) ;
83
+ } ,
84
+ } ;
85
+ this . #timeSync. subscribe ( patchedEntry ) ;
86
+
87
+ const date = this . #timeSync. getTimeSnapshot ( ) ;
88
+ const cacheValue = select ?.( date ) ?? date ;
89
+ this . #selectionCache. set ( id , { value : cacheValue } ) ;
90
+
91
+ return ( ) => this . #timeSync. unsubscribe ( id ) ;
77
92
} ;
78
93
94
+ /**
95
+ * Allows you to grab the result of a selection that has been registered
96
+ * with ReactTimeSync.
97
+ *
98
+ * If this method is called with an ID before a subscription has been
99
+ * registered for that ID, that will cause the method to throw.
100
+ */
79
101
getSelectionSnapshot = < T , > ( id : string ) : T => {
80
- return this . #selectionCache. get ( id ) as T ;
102
+ const cacheEntry = this . #selectionCache. get ( id ) ;
103
+ if ( cacheEntry === undefined ) {
104
+ throw new Error (
105
+ "Trying to retrieve value from selection cache without it being initialized" ,
106
+ ) ;
107
+ }
108
+
109
+ return cacheEntry . value as T ;
81
110
} ;
82
111
}
83
112
@@ -177,7 +206,7 @@ export function useTimeSync<T = Date>(options: UseTimeSyncOptions<T>): T {
177
206
} ) ;
178
207
179
208
// We need to define this callback using useCallback instead of
180
- // useEffectEvent because we want the memoization to be invaliated when the
209
+ // useEffectEvent because we want the memoization to be invalidated when the
181
210
// refresh interval changes. (When the subscription callback changes by
182
211
// reference, that causes useSyncExternalStore to redo the subscription with
183
212
// the new callback). All other values need to be included in the dependency
0 commit comments