Skip to content

Commit

Permalink
merge extra request states in cache
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Dec 28, 2021
1 parent c289c61 commit cae546d
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 27 deletions.
27 changes: 17 additions & 10 deletions src/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ export const useSWRHandler = <Data = any, Error = any>(
const [EVENT_REVALIDATORS, STATE_UPDATERS, MUTATION, FETCH] =
SWRGlobalState.get(cache) as GlobalState

// `key` is the identifier of the SWR `data` state, `keyErr` and
// `keyValidating` are identifiers of `error` and `isValidating`,
// `key` is the identifier of the SWR `data` state, `keyInfo` holds extra
// states such as `error` and `isValidating` inside,
// all of them are derived from `_key`.
// `fnArgs` is an array of arguments parsed from the key, which will be passed
// to the fetcher.
const [key, fnArgs, keyErr, keyValidating] = serialize(_key)
const [key, fnArgs, keyInfo] = serialize(_key)

// If it's the initial render of this hook.
const initialMountedRef = useRef(false)
Expand All @@ -72,14 +72,17 @@ export const useSWRHandler = <Data = any, Error = any>(
const configRef = useRef(config)
const getConfig = () => configRef.current
const isActive = () => getConfig().isVisible() && getConfig().isOnline()
const patchFetchInfo = (info: { isValidating?: boolean; error?: any }) =>
cache.set(keyInfo, mergeObjects(cache.get(keyInfo), info))

// Get the current state that SWR should return.
const cached = cache.get(key)
const fallback = isUndefined(fallbackData)
? config.fallback[key]
: fallbackData
const data = isUndefined(cached) ? fallback : cached
const error = cache.get(keyErr)
const info = cache.get(keyInfo) || {}
const error = info.error

// - Suspense mode and there's stale data for the initial render.
// - Not suspense mode and there is no fallback data and `revalidateIfStale` is enabled.
Expand All @@ -103,7 +106,7 @@ export const useSWRHandler = <Data = any, Error = any>(
// Resolve the current validating state.
const resolveValidating = () => {
if (!key || !fetcher) return false
if (cache.get(keyValidating)) return true
if (info.isValidating) return true

// If it's not mounted yet and it should revalidate on mount, revalidate.
return !initialMountedRef.current && shouldRevalidateOnMount()
Expand Down Expand Up @@ -163,15 +166,17 @@ export const useSWRHandler = <Data = any, Error = any>(
// The new state object when request finishes.
const newState: State<Data, Error> = { isValidating: false }
const finishRequestAndUpdateState = () => {
cache.set(keyValidating, false)
patchFetchInfo({ isValidating: false })
// We can only set state if it's safe (still mounted with the same key).
if (isCurrentKeyMounted()) {
setState(newState)
}
}

// Start fetching. Change the `isValidating` state, update the cache.
cache.set(keyValidating, true)
patchFetchInfo({
isValidating: true
})
setState({ isValidating: true })

try {
Expand Down Expand Up @@ -226,7 +231,9 @@ export const useSWRHandler = <Data = any, Error = any>(
}

// Clear error.
cache.set(keyErr, UNDEFINED)
patchFetchInfo({
error: UNDEFINED
})
newState.error = UNDEFINED

// If there're other mutations(s), overlapped with the current revalidation:
Expand Down Expand Up @@ -290,7 +297,7 @@ export const useSWRHandler = <Data = any, Error = any>(
// Not paused, we continue handling the error. Otherwise discard it.
if (!getConfig().isPaused()) {
// Get a new error, don't use deep comparison for errors.
cache.set(keyErr, err)
patchFetchInfo({ error: err })
newState.error = err as Error

// Error event and retry logic. Only for the actual request, not
Expand Down Expand Up @@ -326,7 +333,7 @@ export const useSWRHandler = <Data = any, Error = any>(

return true
},
// `setState` is immutable, and `eventsCallback`, `fnArgs`, `keyErr`,
// `setState` is immutable, and `eventsCallback`, `fnArgs`, `keyInfo`,
// and `keyValidating` are depending on `key`, so we can exclude them from
// the deps array.
//
Expand Down
8 changes: 4 additions & 4 deletions src/utils/mutate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { serialize } from './serialize'
import { isFunction, isUndefined, UNDEFINED } from './helper'
import { isFunction, isUndefined, mergeObjects, UNDEFINED } from './helper'
import { SWRGlobalState, GlobalState } from './global-state'
import { broadcastState } from './broadcast-state'
import { getTimestamp } from './timestamp'
Expand Down Expand Up @@ -28,7 +28,7 @@ export const internalMutate = async <Data>(
const optimisticData = options.optimisticData

// Serilaize key
const [key, , keyErr] = serialize(_key)
const [key, , keyInfo] = serialize(_key)
if (!key) return

const [, , MUTATION] = SWRGlobalState.get(cache) as GlobalState
Expand All @@ -40,7 +40,7 @@ export const internalMutate = async <Data>(
cache,
key,
cache.get(key),
cache.get(keyErr),
UNDEFINED,
UNDEFINED,
revalidate,
populateCache
Expand Down Expand Up @@ -100,7 +100,7 @@ export const internalMutate = async <Data>(
cache.set(key, data)
}
// Always update or reset the error.
cache.set(keyErr, error)
cache.set(keyInfo, mergeObjects(cache.get(keyInfo), { error }))
}

// Reset the timestamp to mark the mutation has ended.
Expand Down
12 changes: 4 additions & 8 deletions src/utils/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { isFunction } from './helper'

import { Key } from '../types'

export const serialize = (key: Key): [string, any[], string, string] => {
export const serialize = (key: Key): [string, any[], string] => {
if (isFunction(key)) {
try {
key = key()
Expand All @@ -19,14 +19,10 @@ export const serialize = (key: Key): [string, any[], string, string] => {
key =
typeof key == 'string'
? key
: (Array.isArray(key)
? key.length
: key)
: (Array.isArray(key) ? key.length : key)
? stableHash(key)
: ''

const errorKey = key ? '$err$' + key : ''
const isValidatingKey = key ? '$req$' + key : ''

return [key, args, errorKey, isValidatingKey]
const infoKey = key ? '$swr$' + key : ''
return [key, args, infoKey]
}
10 changes: 5 additions & 5 deletions test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -536,16 +536,16 @@ describe('useSWR - local mutation', () => {
})

screen.getByText(message)
const [keyData, , keyErr] = serialize(key)
let cacheError = cache.get(keyErr)
const [keyData, , keyInfo] = serialize(key)
let cacheError = cache.get(keyInfo)?.error
expect(cacheError.message).toMatchInlineSnapshot(`"${message}"`)

// if mutate throws an error synchronously, the cache shouldn't be updated
expect(cache.get(keyData)).toBe(value)

// if mutate succeed, error should be cleared
await act(() => mutate(key, value, false))
cacheError = cache.get(keyErr)
cacheError = cache.get(keyInfo)?.error
expect(cacheError).toMatchInlineSnapshot(`undefined`)
})

Expand Down Expand Up @@ -807,8 +807,8 @@ describe('useSWR - local mutation', () => {
createResponse('data', { delay: 30 })
)
const { cache } = useSWRConfig()
const [, , , keyValidating] = serialize(key)
const cacheIsValidating = cache.get(keyValidating) || false
const [, , keyInfo] = serialize(key)
const cacheIsValidating = cache.get(keyInfo)?.isValidating
return (
<>
<p>data:{data}</p>
Expand Down

0 comments on commit cae546d

Please sign in to comment.