@@ -29,12 +29,10 @@ import {
29
29
} from "utils/TimeSync" ;
30
30
import { useEffectEvent } from "./hookPolyfills" ;
31
31
32
- export {
33
- TARGET_REFRESH_ONE_DAY ,
34
- TARGET_REFRESH_ONE_HOUR ,
35
- TARGET_REFRESH_ONE_MINUTE ,
36
- TARGET_REFRESH_ONE_SECOND ,
37
- } from "utils/TimeSync" ;
32
+ export const TARGET_REFRESH_ONE_SECOND = 1_000 ;
33
+ export const TARGET_REFRESH_ONE_MINUTE = 60 * 1_000 ;
34
+ export const TARGET_REFRESH_ONE_HOUR = 60 * 60 * 1_000 ;
35
+ export const TARGET_REFRESH_ONE_DAY = 24 * 60 * 60 * 1_000 ;
38
36
39
37
type ReactSubscriptionCallback = ( notifyReact : ( ) => void ) => ( ) => void ;
40
38
@@ -88,7 +86,7 @@ class ReactTimeSync implements ReactTimeSyncApi {
88
86
onUpdate : ( newDate ) => {
89
87
const prevSelection = this . getSelectionSnapshot ( id ) ;
90
88
const newSelection = select ?.( newDate ) ?? newDate ;
91
- if ( newSelection === prevSelection ) {
89
+ if ( areValuesDeepEqual ( prevSelection , newSelection ) ) {
92
90
return ;
93
91
}
94
92
@@ -99,7 +97,7 @@ class ReactTimeSync implements ReactTimeSyncApi {
99
97
this . #timeSync. subscribe ( patchedEntry ) ;
100
98
101
99
// Have to seed the selection cache with an initial value so that it's
102
- // safe to get the value from React immediately after the subscription
100
+ // safe for React to get the value immediately after the subscription
103
101
// gets registered
104
102
const date = this . #timeSync. getTimeSnapshot ( ) ;
105
103
const cacheValue = select ?.( date ) ?? date ;
@@ -132,6 +130,11 @@ class ReactTimeSync implements ReactTimeSyncApi {
132
130
return ;
133
131
}
134
132
133
+ // It is very, VERY important that we only change the value in the
134
+ // selection cache when it changes by value. If the state getter
135
+ // function for useSyncExternalStore always receives a new value by
136
+ // reference on every call, that will create an infinite render loop in
137
+ // dev mode
135
138
const dateSnapshot = this . #timeSync. getTimeSnapshot ( ) ;
136
139
const newSelection = select ?.( dateSnapshot ) ?? dateSnapshot ;
137
140
if ( ! areValuesDeepEqual ( newSelection , prevSelection . value ) ) {
@@ -270,10 +273,15 @@ export function useTimeSync(options: UseTimeSyncOptions): Date {
270
273
type UseTimeSyncSelectOptions < T > = Readonly <
271
274
UseTimeSyncOptions & {
272
275
/**
273
- * Allows you to transform any date values received from the TimeSync
274
- * class. Select functions work similarly to the selects from React
275
- * Query and Redux Toolkit – you don't need to memoize them, and they
276
- * will only run when the underlying TimeSync state has changed.
276
+ * Allows you to transform any Date values received from the TimeSync
277
+ * class, while providing render optimizations. Select functions are
278
+ * ALWAYS run during a render to guarantee no wrong data from stale
279
+ * closures.
280
+ *
281
+ * However, when a new time update is dispatched from TimeSync, the hook
282
+ * will use the latest select callback received to transform the value.
283
+ * If the select result has not changed compared to last time
284
+ * (comparing via deep equality), the hook will skip re-rendering.
277
285
*
278
286
* Select functions must not be async. The hook will error out at the
279
287
* type level if you provide one by mistake.
0 commit comments