@@ -49,12 +49,12 @@ const broadcastState: Broadcaster = (
49
49
return Promise . all ( promises ) . then ( ( ) => cache . get ( key ) )
50
50
}
51
51
52
- async function internalMutate < Data = any > (
52
+ const internalMutate = async < Data > (
53
53
cache : Cache ,
54
54
_key : Key ,
55
55
_data ?: Data | Promise < Data | undefined > | MutatorCallback < Data > ,
56
56
shouldRevalidate = true
57
- ) : Promise < Data | undefined > {
57
+ ) = > {
58
58
const [ key , , keyErr ] = serialize ( _key )
59
59
if ( ! key ) return UNDEFINED
60
60
@@ -91,26 +91,27 @@ async function internalMutate<Data = any>(
91
91
}
92
92
}
93
93
94
+ // `_data` is a promise/thenable, resolve the final data first.
94
95
if ( _data && typeof ( _data as Promise < Data > ) . then === 'function' ) {
95
- // `_data` is a promise/thenable, resolve the final data.
96
+ // This means that the mutation is async, we need to check timestamps to
97
+ // avoid race conditions.
96
98
try {
97
99
data = await _data
98
100
} catch ( err ) {
99
101
error = err
100
102
}
103
+
104
+ // Check if other mutations have occurred since we've started this mutation.
105
+ // If there's a race we don't update cache or broadcast the change,
106
+ // just return the data.
107
+ if ( beforeMutationTs !== MUTATION_TS [ key ] ) {
108
+ if ( error ) throw error
109
+ return data
110
+ }
101
111
} else {
102
112
data = _data
103
113
}
104
114
105
- // Check if other mutations have occurred since we've started this mutation.
106
- const shouldAbort = beforeMutationTs !== MUTATION_TS [ key ]
107
-
108
- // If there's a race we don't update cache or broadcast change, just return the data.
109
- if ( shouldAbort ) {
110
- if ( error ) throw error
111
- return data
112
- }
113
-
114
115
if ( ! isUndefined ( data ) ) {
115
116
// update cached data
116
117
cache . set ( key , data )
@@ -161,11 +162,11 @@ const addRevalidator = (
161
162
}
162
163
}
163
164
164
- export function useSWRHandler < Data = any , Error = any > (
165
+ export const useSWRHandler = < Data = any , Error = any > (
165
166
_key : Key ,
166
167
fn : Fetcher < Data > | null ,
167
168
config : typeof defaultConfig & SWRConfiguration < Data , Error >
168
- ) : SWRResponse < Data , Error > {
169
+ ) = > {
169
170
const {
170
171
cache,
171
172
compare,
@@ -457,45 +458,20 @@ export function useSWRHandler<Data = any, Error = any>(
457
458
const keyChanged = initialMountedRef . current
458
459
const softRevalidate = ( ) => revalidate ( { dedupe : true } )
459
460
460
- // Mark the component as mounted and update corresponding refs.
461
- unmountedRef . current = false
462
- keyRef . current = key
463
-
464
- // When `key` updates, reset the state to the initial value
465
- // and trigger a rerender if necessary.
466
- if ( keyChanged ) {
467
- setState ( {
468
- data,
469
- error,
470
- isValidating
471
- } )
472
- }
473
-
474
- // Trigger a revalidation.
475
- if ( keyChanged || shouldRevalidateOnMount ( ) ) {
476
- if ( isUndefined ( data ) || IS_SERVER ) {
477
- // Revalidate immediately.
478
- softRevalidate ( )
479
- } else {
480
- // Delay the revalidate if we have data to return so we won't block
481
- // rendering.
482
- rAF ( softRevalidate )
483
- }
484
- }
485
-
486
461
const isActive = ( ) =>
487
462
configRef . current . isVisible ( ) && configRef . current . isOnline ( )
488
463
489
464
// Add event listeners.
490
- let pending = false
465
+ let nextFocusRevalidatedAt = 0
491
466
const onFocus = ( ) => {
492
- if ( configRef . current . revalidateOnFocus && ! pending && isActive ( ) ) {
493
- pending = true
467
+ const now = Date . now ( )
468
+ if (
469
+ configRef . current . revalidateOnFocus &&
470
+ now > nextFocusRevalidatedAt &&
471
+ isActive ( )
472
+ ) {
473
+ nextFocusRevalidatedAt = now + configRef . current . focusThrottleInterval
494
474
softRevalidate ( )
495
- setTimeout (
496
- ( ) => ( pending = false ) ,
497
- configRef . current . focusThrottleInterval
498
- )
499
475
}
500
476
}
501
477
@@ -534,6 +510,32 @@ export function useSWRHandler<Data = any, Error = any>(
534
510
const unsubReconn = addRevalidator ( RECONNECT_REVALIDATORS , key , onReconnect )
535
511
const unsubUpdate = addRevalidator ( CACHE_REVALIDATORS , key , onUpdate )
536
512
513
+ // Mark the component as mounted and update corresponding refs.
514
+ unmountedRef . current = false
515
+ keyRef . current = key
516
+
517
+ // When `key` updates, reset the state to the initial value
518
+ // and trigger a rerender if necessary.
519
+ if ( keyChanged ) {
520
+ setState ( {
521
+ data,
522
+ error,
523
+ isValidating
524
+ } )
525
+ }
526
+
527
+ // Trigger a revalidation.
528
+ if ( keyChanged || shouldRevalidateOnMount ( ) ) {
529
+ if ( isUndefined ( data ) || IS_SERVER ) {
530
+ // Revalidate immediately.
531
+ softRevalidate ( )
532
+ } else {
533
+ // Delay the revalidate if we have data to return so we won't block
534
+ // rendering.
535
+ rAF ( softRevalidate )
536
+ }
537
+ }
538
+
537
539
// Finally, the component is mounted.
538
540
initialMountedRef . current = true
539
541
@@ -549,36 +551,38 @@ export function useSWRHandler<Data = any, Error = any>(
549
551
550
552
// Polling
551
553
useIsomorphicLayoutEffect ( ( ) => {
552
- let timer : any = 0
553
-
554
- function nextTick ( ) {
555
- if ( refreshInterval ) {
556
- timer = setTimeout ( tick , refreshInterval )
554
+ let timer : any
555
+
556
+ function next ( ) {
557
+ // We only start next interval if `refreshInterval` is not 0, and:
558
+ // - `force` is true, which is the start of polling
559
+ // - or `timer` is not 0, which means the effect wasn't canceled
560
+ if ( refreshInterval && timer !== - 1 ) {
561
+ timer = setTimeout ( execute , refreshInterval )
557
562
}
558
563
}
559
564
560
- async function tick ( ) {
565
+ function execute ( ) {
566
+ // Check if it's OK to execute:
567
+ // Only revalidate when the page is visible, online and not errored.
561
568
if (
562
569
! stateRef . current . error &&
563
570
( refreshWhenHidden || config . isVisible ( ) ) &&
564
571
( refreshWhenOffline || config . isOnline ( ) )
565
572
) {
566
- // only revalidate when the page is visible
567
- // if API request errored, we stop polling in this round
568
- // and let the error retry function handle it
569
- await revalidate ( { dedupe : true } )
573
+ revalidate ( { dedupe : true } ) . then ( ( ) => next ( ) )
574
+ } else {
575
+ // Schedule next interval to check again.
576
+ next ( )
570
577
}
571
-
572
- // Read the latest refreshInterval
573
- if ( timer ) nextTick ( )
574
578
}
575
579
576
- nextTick ( )
580
+ next ( )
577
581
578
582
return ( ) => {
579
583
if ( timer ) {
580
584
clearTimeout ( timer )
581
- timer = 0
585
+ timer = - 1
582
586
}
583
587
}
584
588
} , [ refreshInterval , refreshWhenHidden , refreshWhenOffline , revalidate ] )
@@ -640,13 +644,10 @@ export const mutate = internalMutate.bind(
640
644
defaultConfig . cache
641
645
) as ScopedMutator
642
646
643
- export function createCache < Data > (
647
+ export const createCache = < Data = any > (
644
648
provider : Cache ,
645
649
options ?: Partial < ProviderOptions >
646
- ) : {
647
- cache : Cache
648
- mutate : ScopedMutator < Data >
649
- } {
650
+ ) => {
650
651
const cache = wrapCache < Data > ( provider , options )
651
652
return {
652
653
cache,
0 commit comments