Skip to content

Commit

Permalink
Promote isValidating to the global level of data and error (#569)
Browse files Browse the repository at this point in the history
* Promote isValidating to the global level of data and error

* initialIsValidating to boolean
  • Loading branch information
blackarctic committed Sep 16, 2020
1 parent 8df50bb commit 05755f8
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ esm
.next
.DS_Store
.idea
.vscode
examples/**/yarn.lock
package-lock.json
5 changes: 3 additions & 2 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default class Cache implements CacheInterface {
}

// TODO: introduce namespace for the cache
serializeKey(key: keyInterface): [string, any, string] {
serializeKey(key: keyInterface): [string, any, string, string] {
let args = null
if (typeof key === 'function') {
try {
Expand All @@ -63,8 +63,9 @@ export default class Cache implements CacheInterface {
}

const errorKey = key ? 'err@' + key : ''
const isValidatingKey = key ? 'validating@' + key : ''

return [key, args, errorKey]
return [key, args, errorKey, isValidatingKey]
}

subscribe(listener: cacheListener) {
Expand Down
5 changes: 3 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export type mutateInterface<Data = any> = (
export type broadcastStateInterface<Data = any, Error = any> = (
key: string,
data: Data,
error?: Error
error?: Error,
isValidating?: boolean
) => void
export type responseInterface<Data, Error> = {
data?: Data
Expand Down Expand Up @@ -127,7 +128,7 @@ export interface CacheInterface {
has(key: keyInterface): boolean
delete(key: keyInterface): void
clear(): void
serializeKey(key: keyInterface): [string, any, string]
serializeKey(key: keyInterface): [string, any, string, string]
subscribe(listener: cacheListener): () => void
}

Expand Down
48 changes: 39 additions & 9 deletions src/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,25 @@ if (!IS_SERVER && window.addEventListener) {
const trigger: triggerInterface = (_key, shouldRevalidate = true) => {
// we are ignoring the second argument which correspond to the arguments
// the fetcher will receive when key is an array
const [key, , keyErr] = cache.serializeKey(_key)
const [key, , keyErr, keyValidating] = cache.serializeKey(_key)
if (!key) return Promise.resolve()

const updaters = CACHE_REVALIDATORS[key]

if (key && updaters) {
const currentData = cache.get(key)
const currentError = cache.get(keyErr)
const currentIsValidating = cache.get(keyValidating)
const promises = []
for (let i = 0; i < updaters.length; ++i) {
promises.push(
updaters[i](shouldRevalidate, currentData, currentError, i > 0)
updaters[i](
shouldRevalidate,
currentData,
currentError,
currentIsValidating,
i > 0
)
)
}
// return new updated value
Expand All @@ -95,11 +102,16 @@ const trigger: triggerInterface = (_key, shouldRevalidate = true) => {
return Promise.resolve(cache.get(key))
}

const broadcastState: broadcastStateInterface = (key, data, error) => {
const broadcastState: broadcastStateInterface = (
key,
data,
error,
isValidating
) => {
const updaters = CACHE_REVALIDATORS[key]
if (key && updaters) {
for (let i = 0; i < updaters.length; ++i) {
updaters[i](false, data, error)
updaters[i](false, data, error, isValidating)
}
}
}
Expand Down Expand Up @@ -167,7 +179,9 @@ const mutate: mutateInterface = async (
if (updaters) {
const promises = []
for (let i = 0; i < updaters.length; ++i) {
promises.push(updaters[i](!!shouldRevalidate, data, error, i > 0))
promises.push(
updaters[i](!!shouldRevalidate, data, error, undefined, i > 0)
)
}
// return new updated value
return Promise.all(promises).then(() => {
Expand Down Expand Up @@ -216,7 +230,7 @@ function useSWR<Data = any, Error = any>(
// `key` can change but `fn` shouldn't
// (because `revalidate` only depends on `key`)
// `keyErr` is the cache key for error objects
const [key, fnArgs, keyErr] = cache.serializeKey(_key)
const [key, fnArgs, keyErr, keyValidating] = cache.serializeKey(_key)

config = Object.assign(
{},
Expand All @@ -242,6 +256,7 @@ function useSWR<Data = any, Error = any>(

const initialData = resolveData()
const initialError = cache.get(keyErr)
const initialIsValidating = !!cache.get(keyValidating)

// if a state is accessed (data, error or isValidating),
// we add the state to dependencies so if the state is
Expand All @@ -254,7 +269,7 @@ function useSWR<Data = any, Error = any>(
const stateRef = useRef({
data: initialData,
error: initialError,
isValidating: false
isValidating: initialIsValidating
})

// display the data label in the React DevTools next to SWR hooks
Expand Down Expand Up @@ -338,6 +353,11 @@ function useSWR<Data = any, Error = any>(
dispatch({
isValidating: true
})
cache.set(keyValidating, true)
if (!shouldDeduping) {
// also update other hooks
broadcastState(key, undefined, undefined, true)
}

let newData
let startAt
Expand Down Expand Up @@ -410,6 +430,7 @@ function useSWR<Data = any, Error = any>(

cache.set(key, newData)
cache.set(keyErr, undefined)
cache.set(keyValidating, false)

// new state for the reducer
const newState: actionType<Data, Error> = {
Expand All @@ -431,7 +452,7 @@ function useSWR<Data = any, Error = any>(

if (!shouldDeduping) {
// also update other hooks
broadcastState(key, newData, undefined)
broadcastState(key, newData, undefined, false)
}
} catch (err) {
delete CONCURRENT_PROMISES[key]
Expand All @@ -450,7 +471,7 @@ function useSWR<Data = any, Error = any>(

if (!shouldDeduping) {
// also broadcast to update other hooks
broadcastState(key, undefined, err)
broadcastState(key, undefined, err, false)
}
}

Expand Down Expand Up @@ -537,6 +558,7 @@ function useSWR<Data = any, Error = any>(
shouldRevalidate = true,
updatedData,
updatedError,
updatedIsValidating,
dedupe = true
) => {
// update hook state
Expand All @@ -558,6 +580,14 @@ function useSWR<Data = any, Error = any>(
needUpdate = true
}

if (
typeof updatedIsValidating !== 'undefined' &&
stateRef.current.isValidating !== updatedIsValidating
) {
newState.isValidating = updatedIsValidating
needUpdate = true
}

if (needUpdate) {
dispatch(newState)
}
Expand Down
44 changes: 44 additions & 0 deletions test/use-swr.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,50 @@ describe('useSWR', () => {
expect(container.textContent).toMatchInlineSnapshot(`"err err err"`)
})

it('should broadcast isValidating', async () => {
function useBroadcast3() {
const { isValidating, revalidate } = useSWR(
'broadcast-3',
() => new Promise(res => setTimeout(res, 100)),
{
// need to turn of deduping otherwise
// revalidating will be ignored
dedupingInterval: 10
}
)
return { isValidating, revalidate }
}
function Initiator() {
const { isValidating, revalidate } = useBroadcast3()
useEffect(() => {
const timeout = setTimeout(() => {
revalidate()
}, 200)
return () => clearTimeout(timeout)
}, [])
return <>{isValidating ? 'true' : 'false'}</>
}
function Consumer() {
const { isValidating } = useBroadcast3()
return <>{isValidating ? 'true' : 'false'}</>
}
function Page() {
return (
<>
<Initiator /> <Consumer /> <Consumer />
</>
)
}
const { container } = render(<Page />)
expect(container.textContent).toMatchInlineSnapshot(`"true true true"`)
await act(() => new Promise(res => setTimeout(res, 100)))
expect(container.textContent).toMatchInlineSnapshot(`"false false false"`)
await act(() => new Promise(res => setTimeout(res, 100)))
expect(container.textContent).toMatchInlineSnapshot(`"true true true"`)
await act(() => new Promise(res => setTimeout(res, 100)))
expect(container.textContent).toMatchInlineSnapshot(`"false false false"`)
})

it('should accept object args', async () => {
const obj = { v: 'hello' }
const arr = ['world']
Expand Down

0 comments on commit 05755f8

Please sign in to comment.