Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promote isValidating to the global level of data and error #569

Merged
merged 2 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -232,6 +246,7 @@ function useSWR<Data = any, Error = any>(

const initialData = cache.get(key) || config.initialData
const initialError = cache.get(keyErr)
const initialIsValidating = cache.get(keyValidating)
blackarctic marked this conversation as resolved.
Show resolved Hide resolved

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

const rerender = useState(null)[1]
Expand Down Expand Up @@ -321,6 +336,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 @@ -393,6 +413,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 @@ -414,7 +435,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 @@ -433,7 +454,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 @@ -518,6 +539,7 @@ function useSWR<Data = any, Error = any>(
shouldRevalidate = true,
updatedData,
updatedError,
updatedIsValidating,
dedupe = true
) => {
// update hook state
Expand All @@ -539,6 +561,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