Skip to content

Commit 390fe5e

Browse files
shudinghimanshiLt
authored andcommitted
replace isFallback with isLoading state; optimize code (vercel#1928)
1 parent 75129d4 commit 390fe5e

File tree

8 files changed

+176
-108
lines changed

8 files changed

+176
-108
lines changed

infinite/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>
267267
get isValidating() {
268268
return swr.isValidating
269269
},
270-
get isFallback() {
271-
return swr.isFallback
270+
get isLoading() {
271+
return swr.isLoading
272272
}
273273
} as SWRInfiniteResponse<Data, Error>
274274
}) as unknown as Middleware

src/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as revalidateEvents from './constants'
2+
import { defaultConfig } from './utils/config'
23

34
export type FetcherResponse<Data = unknown> = Data | Promise<Data>
45
export type BareFetcher<Data = unknown> = (
@@ -121,7 +122,8 @@ export type Middleware = (
121122
) => <Data = any, Error = any>(
122123
key: Key,
123124
fetcher: BareFetcher<Data> | null,
124-
config: SWRConfiguration<Data, Error, BareFetcher<Data>>
125+
config: typeof defaultConfig &
126+
SWRConfiguration<Data, Error, BareFetcher<Data>>
125127
) => SWRResponse<Data, Error>
126128

127129
type ArgumentsTuple = [any, ...unknown[]] | readonly [any, ...unknown[]]
@@ -164,6 +166,7 @@ export type State<Data, Error> = {
164166
data?: Data
165167
error?: Error
166168
isValidating?: boolean
169+
isLoading?: boolean
167170
}
168171

169172
export type MutatorFn<Data = any> = (
@@ -220,7 +223,7 @@ export interface SWRResponse<Data = any, Error = any> {
220223
error: Error | undefined
221224
mutate: KeyedMutator<Data>
222225
isValidating: boolean
223-
isFallback: boolean
226+
isLoading: boolean
224227
}
225228

226229
export type KeyLoader<Args extends Arguments = Arguments> =

src/use-swr.ts

Lines changed: 52 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export const useSWRHandler = <Data = any, Error = any>(
8787
const getConfig = () => configRef.current
8888
const isActive = () => getConfig().isVisible() && getConfig().isOnline()
8989

90-
const [get, set] = createCacheHelper<Data>(cache, key)
90+
const [getCache, setCache] = createCacheHelper<Data>(cache, key)
9191

9292
// Get the current state that SWR should return.
93-
const cached = get()
93+
const cached = getCache()
9494
const cachedData = cached.data
9595
const fallback = isUndefined(fallbackData)
9696
? config.fallback[key]
@@ -103,7 +103,7 @@ export const useSWRHandler = <Data = any, Error = any>(
103103
// - Suspense mode and there's stale data for the initial render.
104104
// - Not suspense mode and there is no fallback data and `revalidateIfStale` is enabled.
105105
// - `revalidateIfStale` is enabled but `data` is not defined.
106-
const shouldRevalidate = () => {
106+
const shouldDoInitialRevalidation = (() => {
107107
// If `revalidateOnMount` is set, we take the value directly.
108108
if (isInitialMount && !isUndefined(revalidateOnMount))
109109
return revalidateOnMount
@@ -119,22 +119,24 @@ export const useSWRHandler = <Data = any, Error = any>(
119119
// If there is no stale data, we need to revalidate on mount;
120120
// If `revalidateIfStale` is set to true, we will always revalidate.
121121
return isUndefined(data) || config.revalidateIfStale
122-
}
123-
124-
// Resolve the current validating state.
125-
const resolveValidating = () => {
126-
if (!key || !fetcher) return false
127-
if (cached.isValidating) return true
128-
129-
// If it's not mounted yet and it should revalidate on mount, revalidate.
130-
return isInitialMount && shouldRevalidate()
131-
}
132-
const isValidating = resolveValidating()
122+
})()
123+
124+
// Resolve the default validating state:
125+
// If it's able to validate, and it should revalidate on mount, this will be true.
126+
const defaultValidatingState = !!(
127+
key &&
128+
fetcher &&
129+
isInitialMount &&
130+
shouldDoInitialRevalidation
131+
)
132+
const isValidating = cached.isValidating || defaultValidatingState
133+
const isLoading = cached.isLoading || defaultValidatingState
133134

134135
const currentState = {
135136
data,
136137
error,
137-
isValidating
138+
isValidating,
139+
isLoading
138140
}
139141
const [stateRef, stateDependencies, setState] = useStateWithDeps(currentState)
140142

@@ -171,6 +173,18 @@ export const useSWRHandler = <Data = any, Error = any>(
171173
key === keyRef.current &&
172174
initialMountedRef.current
173175

176+
// The final state object when request finishes.
177+
const finalState: State<Data, Error> = {
178+
isValidating: false,
179+
isLoading: false
180+
}
181+
const finishRequestAndUpdateState = () => {
182+
setCache(finalState)
183+
// We can only set state if it's safe (still mounted with the same key).
184+
if (isCurrentKeyMounted()) {
185+
setState(finalState)
186+
}
187+
}
174188
const cleanupState = () => {
175189
// Check if it's still the same request before deleting.
176190
const requestInfo = FETCH[key]
@@ -179,19 +193,15 @@ export const useSWRHandler = <Data = any, Error = any>(
179193
}
180194
}
181195

182-
// The new state object when request finishes.
183-
const newState: State<Data, Error> = { isValidating: false }
184-
const finishRequestAndUpdateState = () => {
185-
set({ isValidating: false })
186-
// We can only set state if it's safe (still mounted with the same key).
187-
if (isCurrentKeyMounted()) {
188-
setState(newState)
189-
}
190-
}
191-
192196
// Start fetching. Change the `isValidating` state, update the cache.
193-
set({ isValidating: true })
194-
setState({ isValidating: true })
197+
const initialState: State<Data, Error> = { isValidating: true }
198+
// It is in the `isLoading` state, if and only if there is no cached data.
199+
// This bypasses fallback data and laggy data.
200+
if (isUndefined(getCache().data)) {
201+
initialState.isLoading = true
202+
}
203+
setCache(initialState)
204+
setState(initialState)
195205

196206
try {
197207
if (shouldStartNewRequest) {
@@ -203,7 +213,7 @@ export const useSWRHandler = <Data = any, Error = any>(
203213

204214
// If no cache being rendered currently (it shows a blank page),
205215
// we trigger the loading slow event.
206-
if (config.loadingTimeout && isUndefined(get().data)) {
216+
if (config.loadingTimeout && isUndefined(getCache().data)) {
207217
setTimeout(() => {
208218
if (loading && isCurrentKeyMounted()) {
209219
getConfig().onLoadingSlow(key, config)
@@ -246,8 +256,7 @@ export const useSWRHandler = <Data = any, Error = any>(
246256
}
247257

248258
// Clear error.
249-
set({ error: UNDEFINED })
250-
newState.error = UNDEFINED
259+
finalState.error = UNDEFINED
251260

252261
// If there're other mutations(s), overlapped with the current revalidation:
253262
// case 1:
@@ -283,19 +292,13 @@ export const useSWRHandler = <Data = any, Error = any>(
283292
// Deep compare with latest state to avoid extra re-renders.
284293
// For local state, compare and assign.
285294
if (!compare(stateRef.current.data, newData)) {
286-
newState.data = newData
295+
finalState.data = newData
287296
} else {
288-
// data and newData are deeply equal
289-
// it should be safe to broadcast the stale data
290-
newState.data = stateRef.current.data
297+
// `data` and `newData` are deeply equal (serialized value).
298+
// So it should be safe to broadcast the stale data to keep referential equality (===).
299+
finalState.data = stateRef.current.data
291300
// At the end of this function, `broadcastState` invokes the `onStateUpdate` function,
292-
// which takes care of avoiding the re-render
293-
}
294-
295-
// For global state, it's possible that the key has changed.
296-
// https://github.com/vercel/swr/pull/1058
297-
if (!compare(get().data, newData)) {
298-
set({ data: newData })
301+
// which takes care of avoiding the re-render.
299302
}
300303

301304
// Trigger the successful callback if it's the original request.
@@ -313,8 +316,7 @@ export const useSWRHandler = <Data = any, Error = any>(
313316
// Not paused, we continue handling the error. Otherwise discard it.
314317
if (!currentConfig.isPaused()) {
315318
// Get a new error, don't use deep comparison for errors.
316-
set({ error: err })
317-
newState.error = err as Error
319+
finalState.error = err as Error
318320

319321
// Error event and retry logic. Only for the actual request, not
320322
// deduped ones.
@@ -354,7 +356,7 @@ export const useSWRHandler = <Data = any, Error = any>(
354356
// Here is the source of the request, need to tell all other hooks to
355357
// update their states.
356358
if (isCurrentKeyMounted() && shouldStartNewRequest) {
357-
broadcastState(cache, key, { ...newState, isValidating: false })
359+
broadcastState(cache, key, finalState)
358360
}
359361

360362
return true
@@ -396,7 +398,6 @@ export const useSWRHandler = <Data = any, Error = any>(
396398
useIsomorphicLayoutEffect(() => {
397399
if (!key) return
398400

399-
const keyChanged = key !== keyRef.current
400401
const softRevalidate = revalidate.bind(UNDEFINED, WITH_DEDUPE)
401402

402403
// Expose state updater to global event listeners. So we can update hook's
@@ -451,18 +452,8 @@ export const useSWRHandler = <Data = any, Error = any>(
451452
keyRef.current = key
452453
initialMountedRef.current = true
453454

454-
// When `key` updates, reset the state to the initial value
455-
// and trigger a rerender if necessary.
456-
if (keyChanged) {
457-
setState({
458-
data,
459-
error,
460-
isValidating
461-
})
462-
}
463-
464455
// Trigger a revalidation.
465-
if (shouldRevalidate()) {
456+
if (shouldDoInitialRevalidation) {
466457
if (isUndefined(data) || IS_SERVER) {
467458
// Revalidate immediately.
468459
softRevalidate()
@@ -480,7 +471,7 @@ export const useSWRHandler = <Data = any, Error = any>(
480471
unsubUpdate()
481472
unsubEvents()
482473
}
483-
}, [key, revalidate])
474+
}, [key])
484475

485476
// Polling
486477
useIsomorphicLayoutEffect(() => {
@@ -524,7 +515,7 @@ export const useSWRHandler = <Data = any, Error = any>(
524515
timer = -1
525516
}
526517
}
527-
}, [refreshInterval, refreshWhenHidden, refreshWhenOffline, revalidate])
518+
}, [refreshInterval, refreshWhenHidden, refreshWhenOffline, key])
528519

529520
// Display debug info in React DevTools.
530521
useDebugValue(data)
@@ -555,11 +546,9 @@ export const useSWRHandler = <Data = any, Error = any>(
555546
stateDependencies.isValidating = true
556547
return isValidating
557548
},
558-
get isFallback() {
559-
stateDependencies.data = true
560-
// `isFallback` is only true when we are displaying a value other than
561-
// the cached one.
562-
return data !== cachedData
549+
get isLoading() {
550+
stateDependencies.isLoading = true
551+
return isLoading
563552
}
564553
} as SWRResponse<Data, Error>
565554
}

src/utils/env.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useLayoutEffect } from 'react'
2-
import { hasRequestAnimationFrame, hasWindow } from './helper'
2+
import { hasRequestAnimationFrame, isWindowDefined } from './helper'
33

4-
export const IS_SERVER = !hasWindow() || 'Deno' in window
4+
export const IS_SERVER = !isWindowDefined || 'Deno' in window
55

66
// Polyfill requestAnimationFrame
77
export const rAF = (f: (...args: any[]) => void) =>

src/utils/helper.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const noop = () => {}
22

33
// Using noop() as the undefined value as undefined can possibly be replaced
4-
// by something else. Prettier ignore and extra parentheses are necessary here
4+
// by something else. Prettier ignore and extra parentheses are necessary here
55
// to ensure that tsc doesn't remove the __NOINLINE__ comment.
66
// prettier-ignore
77
export const UNDEFINED = (/*#__NOINLINE__*/ noop()) as undefined
@@ -15,7 +15,7 @@ export const mergeObjects = (a: any, b: any) => OBJECT.assign({}, a, b)
1515
const STR_UNDEFINED = 'undefined'
1616

1717
// NOTE: Use function to guarantee it's re-evaluated between jsdom and node runtime for tests.
18-
export const hasWindow = () => typeof window != STR_UNDEFINED
19-
export const hasDocument = () => typeof document != STR_UNDEFINED
18+
export const isWindowDefined = typeof window != STR_UNDEFINED
19+
export const isDocumentDefined = typeof document != STR_UNDEFINED
2020
export const hasRequestAnimationFrame = () =>
21-
hasWindow() && typeof window['requestAnimationFrame'] != STR_UNDEFINED
21+
isWindowDefined && typeof window['requestAnimationFrame'] != STR_UNDEFINED

src/utils/web-preset.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ProviderConfiguration } from '../types'
2-
import { isUndefined, noop, hasWindow, hasDocument } from './helper'
2+
import { isUndefined, noop, isWindowDefined, isDocumentDefined } from './helper'
33

44
/**
55
* Due to bug https://bugs.chromium.org/p/chromium/issues/detail?id=678075,
@@ -11,34 +11,30 @@ import { isUndefined, noop, hasWindow, hasDocument } from './helper'
1111
let online = true
1212
const isOnline = () => online
1313

14-
const hasWin = hasWindow()
15-
const hasDoc = hasDocument()
16-
1714
// For node and React Native, `add/removeEventListener` doesn't exist on window.
18-
const onWindowEvent =
19-
hasWin && window.addEventListener
20-
? window.addEventListener.bind(window)
21-
: noop
22-
const onDocumentEvent = hasDoc ? document.addEventListener.bind(document) : noop
23-
const offWindowEvent =
24-
hasWin && window.removeEventListener
25-
? window.removeEventListener.bind(window)
26-
: noop
27-
const offDocumentEvent = hasDoc
28-
? document.removeEventListener.bind(document)
29-
: noop
15+
const [onWindowEvent, offWindowEvent] =
16+
isWindowDefined && window.addEventListener
17+
? [
18+
window.addEventListener.bind(window),
19+
window.removeEventListener.bind(window)
20+
]
21+
: [noop, noop]
3022

3123
const isVisible = () => {
32-
const visibilityState = hasDoc && document.visibilityState
24+
const visibilityState = isDocumentDefined && document.visibilityState
3325
return isUndefined(visibilityState) || visibilityState !== 'hidden'
3426
}
3527

3628
const initFocus = (callback: () => void) => {
3729
// focus revalidate
38-
onDocumentEvent('visibilitychange', callback)
30+
if (isDocumentDefined) {
31+
document.addEventListener('visibilitychange', callback)
32+
}
3933
onWindowEvent('focus', callback)
4034
return () => {
41-
offDocumentEvent('visibilitychange', callback)
35+
if (isDocumentDefined) {
36+
document.removeEventListener('visibilitychange', callback)
37+
}
4238
offWindowEvent('focus', callback)
4339
}
4440
}

test/use-swr-cache.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,13 @@ describe('useSWR - cache provider', () => {
198198
it('should support fallback values with custom provider', async () => {
199199
const key = createKey()
200200
function Page() {
201-
const { data, isFallback } = useSWR(key, async () => {
201+
const { data, isLoading } = useSWR(key, async () => {
202202
await sleep(10)
203203
return 'data'
204204
})
205205
return (
206206
<>
207-
{String(data)},{String(isFallback)}
207+
{String(data)},{String(isLoading)}
208208
</>
209209
)
210210
}

0 commit comments

Comments
 (0)