1
1
import { useCallback , useRef , useDebugValue } from 'react'
2
2
import defaultConfig from './utils/config'
3
- import { provider as defaultProvider } from './utils/web-preset'
4
- import { wrapCache } from './utils/cache'
3
+ import { wrapCache , SWRGlobalState , GlobalState } from './utils/cache'
5
4
import { IS_SERVER , rAF , useIsomorphicLayoutEffect } from './utils/env'
6
5
import { serialize } from './utils/serialize'
7
6
import { isUndefined , UNDEFINED } from './utils/helper'
@@ -22,48 +21,13 @@ import {
22
21
Cache ,
23
22
ScopedMutator ,
24
23
SWRHook ,
24
+ Revalidator ,
25
25
ProviderOptions
26
26
} from './types'
27
27
28
- type Revalidator = ( ...args : any [ ] ) => void
29
-
30
28
// Generate strictly increasing timestamps.
31
29
let __timestamp = 0
32
30
33
- // Global state used to deduplicate requests and store listeners
34
- const SWRGlobalState = new WeakMap < Cache , any > ( )
35
- const getGlobalState = ( cache : Cache ) => {
36
- if ( ! SWRGlobalState . has ( cache ) ) {
37
- SWRGlobalState . set ( cache , [ { } , { } , { } , { } , { } , { } , { } ] )
38
- }
39
- return SWRGlobalState . get ( cache ) as [
40
- Record < string , Revalidator [ ] > , // FOCUS_REVALIDATORS
41
- Record < string , Revalidator [ ] > , // RECONNECT_REVALIDATORS
42
- Record < string , Updater [ ] > , // CACHE_REVALIDATORS
43
- Record < string , number > , // MUTATION_TS
44
- Record < string , number > , // MUTATION_END_TS
45
- Record < string , any > , // CONCURRENT_PROMISES
46
- Record < string , number > // CONCURRENT_PROMISES_TS
47
- ]
48
- }
49
-
50
- function setupGlobalEvents ( cache : Cache , _opts : Partial < ProviderOptions > = { } ) {
51
- if ( IS_SERVER ) return
52
- const opts = { ...defaultProvider , ..._opts }
53
- const [ FOCUS_REVALIDATORS , RECONNECT_REVALIDATORS ] = getGlobalState ( cache )
54
- const revalidate = ( revalidators : Record < string , Revalidator [ ] > ) => {
55
- for ( const key in revalidators ) {
56
- if ( revalidators [ key ] [ 0 ] ) revalidators [ key ] [ 0 ] ( )
57
- }
58
- }
59
-
60
- opts . setupOnFocus ( ( ) => revalidate ( FOCUS_REVALIDATORS ) )
61
- opts . setupOnReconnect ( ( ) => revalidate ( RECONNECT_REVALIDATORS ) )
62
- }
63
-
64
- // Setup DOM events listeners for `focus` and `reconnect` actions
65
- setupGlobalEvents ( defaultConfig . cache )
66
-
67
31
const broadcastState : Broadcaster = (
68
32
cache : Cache ,
69
33
key ,
@@ -72,7 +36,7 @@ const broadcastState: Broadcaster = (
72
36
isValidating ,
73
37
shouldRevalidate = false
74
38
) => {
75
- const [ , , CACHE_REVALIDATORS ] = getGlobalState ( cache )
39
+ const [ , , CACHE_REVALIDATORS ] = SWRGlobalState . get ( cache ) as GlobalState
76
40
const updaters = CACHE_REVALIDATORS [ key ]
77
41
const promises = [ ]
78
42
if ( updaters ) {
@@ -94,7 +58,9 @@ async function internalMutate<Data = any>(
94
58
const [ key , , keyErr ] = serialize ( _key )
95
59
if ( ! key ) return UNDEFINED
96
60
97
- const [ , , , MUTATION_TS , MUTATION_END_TS ] = getGlobalState ( cache )
61
+ const [ , , , MUTATION_TS , MUTATION_END_TS ] = SWRGlobalState . get (
62
+ cache
63
+ ) as GlobalState
98
64
99
65
// if there is no new data to update, let's just revalidate the key
100
66
if ( isUndefined ( _data ) ) {
@@ -108,18 +74,14 @@ async function internalMutate<Data = any>(
108
74
)
109
75
}
110
76
111
- // update global timestamps
112
- MUTATION_TS [ key ] = ++ __timestamp
113
- MUTATION_END_TS [ key ] = 0
114
-
115
- // track timestamps before await asynchronously
116
- const beforeMutationTs = MUTATION_TS [ key ]
117
-
118
77
let data : any , error : unknown
119
- let isAsyncMutation = false
78
+
79
+ // Update global timestamps.
80
+ const beforeMutationTs = ( MUTATION_TS [ key ] = ++ __timestamp )
81
+ MUTATION_END_TS [ key ] = 0
120
82
121
83
if ( typeof _data === 'function' ) {
122
- // `_data` is a function, call it passing current cache value
84
+ // `_data` is a function, call it passing current cache value.
123
85
try {
124
86
_data = ( _data as MutatorCallback < Data > ) ( cache . get ( key ) )
125
87
} catch ( err ) {
@@ -130,8 +92,7 @@ async function internalMutate<Data = any>(
130
92
}
131
93
132
94
if ( _data && typeof ( _data as Promise < Data > ) . then === 'function' ) {
133
- // `_data` is a promise
134
- isAsyncMutation = true
95
+ // `_data` is a promise/thenable, resolve the final data.
135
96
try {
136
97
data = await _data
137
98
} catch ( err ) {
@@ -141,32 +102,25 @@ async function internalMutate<Data = any>(
141
102
data = _data
142
103
}
143
104
144
- const shouldAbort = ( ) : boolean | void => {
145
- // check if other mutations have occurred since we've started this mutation
146
- if ( beforeMutationTs !== MUTATION_TS [ key ] ) {
147
- if ( error ) throw error
148
- return true
149
- }
150
- }
105
+ // Check if other mutations have occurred since we've started this mutation.
106
+ const shouldAbort = beforeMutationTs !== MUTATION_TS [ key ]
151
107
152
- // If there's a race we don't update cache or broadcast change, just return the data
153
- if ( shouldAbort ( ) ) return data
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
+ }
154
113
155
114
if ( ! isUndefined ( data ) ) {
156
115
// update cached data
157
116
cache . set ( key , data )
158
117
}
159
- // Always update or reset the error
118
+ // Always update or reset the error.
160
119
cache . set ( keyErr , error )
161
120
162
121
// Reset the timestamp to mark the mutation has ended
163
122
MUTATION_END_TS [ key ] = ++ __timestamp
164
123
165
- if ( ! isAsyncMutation ) {
166
- // We skip broadcasting if there's another mutation happened synchronously
167
- if ( shouldAbort ( ) ) return data
168
- }
169
-
170
124
// Update existing SWR Hooks' internal states:
171
125
return broadcastState (
172
126
cache ,
@@ -185,9 +139,9 @@ async function internalMutate<Data = any>(
185
139
// Add a callback function to a list of keyed revalidation functions and returns
186
140
// the unregister function.
187
141
const addRevalidator = (
188
- revalidators : Record < string , Revalidator [ ] > ,
142
+ revalidators : Record < string , ( Revalidator | Updater < any > ) [ ] > ,
189
143
key : string ,
190
- callback : Revalidator
144
+ callback : Revalidator | Updater < any >
191
145
) => {
192
146
if ( ! revalidators [ key ] ) {
193
147
revalidators [ key ] = [ callback ]
@@ -223,6 +177,7 @@ export function useSWRHandler<Data = any, Error = any>(
223
177
refreshWhenHidden,
224
178
refreshWhenOffline
225
179
} = config
180
+
226
181
const [
227
182
FOCUS_REVALIDATORS ,
228
183
RECONNECT_REVALIDATORS ,
@@ -231,7 +186,7 @@ export function useSWRHandler<Data = any, Error = any>(
231
186
MUTATION_END_TS ,
232
187
CONCURRENT_PROMISES ,
233
188
CONCURRENT_PROMISES_TS
234
- ] = getGlobalState ( cache )
189
+ ] = SWRGlobalState . get ( cache ) as GlobalState
235
190
236
191
// `key` is the identifier of the SWR `data` state.
237
192
// `keyErr` and `keyValidating` are identifiers of `error` and `isValidating`
@@ -248,11 +203,8 @@ export function useSWRHandler<Data = any, Error = any>(
248
203
const configRef = useRef ( config )
249
204
250
205
// Get the current state that SWR should return.
251
- const resolveData = ( ) => {
252
- const cachedData = cache . get ( key )
253
- return isUndefined ( cachedData ) ? initialData : cachedData
254
- }
255
- const data = resolveData ( )
206
+ const cachedData = cache . get ( key )
207
+ const data = isUndefined ( cachedData ) ? initialData : cachedData
256
208
const error = cache . get ( keyErr )
257
209
258
210
// A revalidation must be triggered when mounted if:
@@ -278,10 +230,7 @@ export function useSWRHandler<Data = any, Error = any>(
278
230
}
279
231
const isValidating = resolveValidating ( )
280
232
281
- const [ stateRef , stateDependenciesRef , setState ] = useStateWithDeps <
282
- Data ,
283
- Error
284
- > (
233
+ const [ stateRef , stateDependencies , setState ] = useStateWithDeps < Data , Error > (
285
234
{
286
235
data,
287
236
error,
@@ -293,14 +242,15 @@ export function useSWRHandler<Data = any, Error = any>(
293
242
// The revalidation function is a carefully crafted wrapper of the original
294
243
// `fetcher`, to correctly handle the many edge cases.
295
244
const revalidate = useCallback (
296
- async ( revalidateOpts : RevalidatorOptions = { } ) : Promise < boolean > => {
245
+ async ( revalidateOpts ? : RevalidatorOptions ) : Promise < boolean > => {
297
246
if ( ! key || ! fn || unmountedRef . current || configRef . current . isPaused ( ) ) {
298
247
return false
299
248
}
300
249
301
- const { retryCount , dedupe } = revalidateOpts
302
-
250
+ let newData : Data
251
+ let startAt : number
303
252
let loading = true
253
+ const { retryCount, dedupe } = revalidateOpts || { }
304
254
const shouldDeduping = ! isUndefined ( CONCURRENT_PROMISES [ key ] ) && dedupe
305
255
306
256
// Do unmount check for callbacks:
@@ -329,9 +279,6 @@ export function useSWRHandler<Data = any, Error = any>(
329
279
)
330
280
}
331
281
332
- let newData : Data
333
- let startAt : number
334
-
335
282
if ( shouldDeduping ) {
336
283
// There's already an ongoing request, this one needs to be
337
284
// deduplicated.
@@ -362,8 +309,9 @@ export function useSWRHandler<Data = any, Error = any>(
362
309
363
310
// trigger the success event,
364
311
// only do this for the original request.
365
- if ( isCallbackSafe ( ) )
312
+ if ( isCallbackSafe ( ) ) {
366
313
configRef . current . onSuccess ( newData , key , config )
314
+ }
367
315
}
368
316
369
317
// if there're other ongoing request(s), started after the current one,
@@ -486,6 +434,16 @@ export function useSWRHandler<Data = any, Error = any>(
486
434
[ key ]
487
435
)
488
436
437
+ // `mutate`, but bound to the current key.
438
+ const boundMutate : SWRResponse < Data , Error > [ 'mutate' ] = useCallback (
439
+ ( newData , shouldRevalidate ) => {
440
+ return internalMutate ( cache , keyRef . current , newData , shouldRevalidate )
441
+ } ,
442
+ // `cache` isn't allowed to change during the lifecycle
443
+ // eslint-disable-next-line react-hooks/exhaustive-deps
444
+ [ ]
445
+ )
446
+
489
447
// Always update config.
490
448
useIsomorphicLayoutEffect ( ( ) => {
491
449
configRef . current = config
@@ -525,13 +483,13 @@ export function useSWRHandler<Data = any, Error = any>(
525
483
}
526
484
}
527
485
528
- const isVisible = ( ) =>
486
+ const isActive = ( ) =>
529
487
configRef . current . isDocumentVisible ( ) && configRef . current . isOnline ( )
530
488
531
489
// Add event listeners.
532
490
let pending = false
533
491
const onFocus = ( ) => {
534
- if ( configRef . current . revalidateOnFocus && ! pending && isVisible ( ) ) {
492
+ if ( configRef . current . revalidateOnFocus && ! pending && isActive ( ) ) {
535
493
pending = true
536
494
softRevalidate ( )
537
495
setTimeout (
@@ -541,8 +499,8 @@ export function useSWRHandler<Data = any, Error = any>(
541
499
}
542
500
}
543
501
544
- const onReconnect = ( ) => {
545
- if ( configRef . current . revalidateOnReconnect && isVisible ( ) ) {
502
+ const onReconnect : Revalidator = ( ) => {
503
+ if ( configRef . current . revalidateOnReconnect && isActive ( ) ) {
546
504
softRevalidate ( )
547
505
}
548
506
}
@@ -625,6 +583,9 @@ export function useSWRHandler<Data = any, Error = any>(
625
583
}
626
584
} , [ refreshInterval , refreshWhenHidden , refreshWhenOffline , revalidate ] )
627
585
586
+ // Display debug info in React DevTools.
587
+ useDebugValue ( data )
588
+
628
589
// In Suspense mode, we can't return the empty `data` state.
629
590
// If there is `error`, the `error` needs to be thrown to the error boundary.
630
591
// If there is no `error`, the `revalidation` promise needs to be thrown to
@@ -633,21 +594,6 @@ export function useSWRHandler<Data = any, Error = any>(
633
594
throw isUndefined ( error ) ? revalidate ( { dedupe : true } ) : error
634
595
}
635
596
636
- // `mutate`, but bound to the current key.
637
- const boundMutate : SWRResponse < Data , Error > [ 'mutate' ] = useCallback (
638
- ( newData , shouldRevalidate ) => {
639
- return internalMutate ( cache , keyRef . current , newData , shouldRevalidate )
640
- } ,
641
- // `cache` isn't allowed to change during the lifecycle
642
- // eslint-disable-next-line react-hooks/exhaustive-deps
643
- [ ]
644
- )
645
-
646
- // Display debug info in React DevTools.
647
- useDebugValue ( data )
648
-
649
- const currentStateDependencies = stateDependenciesRef . current
650
-
651
597
// Define the SWR state.
652
598
// `revalidate` will be deprecated in the 1.x release
653
599
// because `mutate()` covers the same use case of `revalidate()`.
@@ -660,21 +606,21 @@ export function useSWRHandler<Data = any, Error = any>(
660
606
{
661
607
data : {
662
608
get : function ( ) {
663
- currentStateDependencies . data = true
609
+ stateDependencies . data = true
664
610
return data
665
611
} ,
666
612
enumerable : true
667
613
} ,
668
614
error : {
669
615
get : function ( ) {
670
- currentStateDependencies . error = true
616
+ stateDependencies . error = true
671
617
return error
672
618
} ,
673
619
enumerable : true
674
620
} ,
675
621
isValidating : {
676
622
get : function ( ) {
677
- currentStateDependencies . isValidating = true
623
+ stateDependencies . isValidating = true
678
624
return isValidating
679
625
} ,
680
626
enumerable : true
@@ -690,7 +636,7 @@ export const SWRConfig = Object.defineProperty(ConfigProvider, 'default', {
690
636
}
691
637
692
638
export const mutate = internalMutate . bind (
693
- null ,
639
+ UNDEFINED ,
694
640
defaultConfig . cache
695
641
) as ScopedMutator
696
642
@@ -701,11 +647,10 @@ export function createCache<Data>(
701
647
cache : Cache
702
648
mutate : ScopedMutator < Data >
703
649
} {
704
- const cache = wrapCache < Data > ( provider )
705
- setupGlobalEvents ( cache , options )
650
+ const cache = wrapCache < Data > ( provider , options )
706
651
return {
707
652
cache,
708
- mutate : internalMutate . bind ( null , cache ) as ScopedMutator < Data >
653
+ mutate : internalMutate . bind ( UNDEFINED , cache ) as ScopedMutator < Data >
709
654
}
710
655
}
711
656
0 commit comments