Skip to content

Commit 03f76c3

Browse files
committed
Merge branch 'master' into fix-staleRef
2 parents f2aee6b + ff4b387 commit 03f76c3

7 files changed

+147
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "swr",
3-
"version": "0.3.11",
3+
"version": "0.4.0",
44
"description": "React Hooks library for remote data fetching",
55
"main": "./dist/index.js",
66
"module": "./esm/index.js",

src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ const defaultConfig: ConfigInterface = {
7070

7171
fetcher: webPreset.fetcher,
7272
isOnline: webPreset.isOnline,
73-
isDocumentVisible: webPreset.isDocumentVisible
73+
isDocumentVisible: webPreset.isDocumentVisible,
74+
isPaused: () => false
7475
}
7576

7677
export { cache }

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ConfigInterface<
2222

2323
isOnline?: () => boolean
2424
isDocumentVisible?: () => boolean
25+
isPaused?: () => boolean
2526
onLoadingSlow?: (key: string, config: ConfigInterface<Data, Error>) => void
2627
onSuccess?: (
2728
data: Data,

src/use-swr-infinite.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SWRConfigContext from './swr-config-context'
55
import useSWR from './use-swr'
66

77
import { keyType, fetcherFn, ConfigInterface, responseInterface } from './types'
8+
89
type KeyLoader<Data = any> = (
910
index: number,
1011
previousPageData: Data | null
@@ -151,7 +152,7 @@ function useSWRInfinite<Data = any, Error = any>(
151152
const shouldRevalidatePage =
152153
revalidateAll ||
153154
force ||
154-
(typeof force === 'undefined' && i === 0) ||
155+
(typeof force === 'undefined' && i === 0 && originalData) ||
155156
(originalData && !config.compare(originalData[i], pageData)) ||
156157
typeof pageData === 'undefined'
157158

src/use-swr.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ function useSWR<Data = any, Error = any>(
379379
): Promise<boolean> => {
380380
if (!key || !fn) return false
381381
if (unmountedRef.current) return false
382+
if (configRef.current.isPaused()) return false
382383
revalidateOpts = Object.assign({ dedupe: false }, revalidateOpts)
383384

384385
let loading = true
@@ -502,6 +503,12 @@ function useSWR<Data = any, Error = any>(
502503
} catch (err) {
503504
delete CONCURRENT_PROMISES[key]
504505
delete CONCURRENT_PROMISES_TS[key]
506+
if (configRef.current.isPaused()) {
507+
dispatch({
508+
isValidating: false
509+
})
510+
return false
511+
}
505512

506513
cache.set(keyValidating, false)
507514
cache.set(keyErr, err)
@@ -686,15 +693,18 @@ function useSWR<Data = any, Error = any>(
686693
await revalidate({ dedupe: true })
687694
}
688695
// Read the latest refreshInterval
689-
if (configRef.current.refreshInterval) {
696+
if (configRef.current.refreshInterval && timer) {
690697
timer = setTimeout(tick, configRef.current.refreshInterval)
691698
}
692699
}
693700
if (configRef.current.refreshInterval) {
694701
timer = setTimeout(tick, configRef.current.refreshInterval)
695702
}
696703
return () => {
697-
if (timer) clearTimeout(timer)
704+
if (timer) {
705+
clearTimeout(timer)
706+
timer = null
707+
}
698708
}
699709
}, [
700710
config.refreshInterval,

test/use-swr-infinite.test.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
act
77
} from '@testing-library/react'
88

9-
import { useSWRInfinite } from '../src'
9+
import { useSWRInfinite, mutate } from '../src'
1010

1111
describe('useSWRInfinite', () => {
1212
it('should render the first page component', async () => {
@@ -59,7 +59,7 @@ describe('useSWRInfinite', () => {
5959
let pageData = ['apple', 'banana', 'pineapple']
6060

6161
function Page() {
62-
const { data, mutate } = useSWRInfinite<string, string>(
62+
const { data, mutate: boundMutate } = useSWRInfinite<string, string>(
6363
index => [`pagetest-3`, index],
6464
async (_, index) => {
6565
await new Promise(res => setTimeout(res, 100))
@@ -74,7 +74,7 @@ describe('useSWRInfinite', () => {
7474
<div
7575
onClick={() => {
7676
// reload the entire list
77-
mutate()
77+
boundMutate()
7878
}}
7979
>
8080
{data}
@@ -441,4 +441,26 @@ describe('useSWRInfinite', () => {
441441
expect(setSize).toEqual(setters[0])
442442
}
443443
})
444+
445+
it('should share initial cache from `useSWR`', async () => {
446+
const cachedData = new Date().toISOString()
447+
mutate('shared-cache-0', cachedData)
448+
449+
function Page() {
450+
const { data } = useSWRInfinite<string, string>(
451+
index => `shared-cache-${index}`,
452+
async () => {
453+
await new Promise(res => setTimeout(res, 200))
454+
return cachedData
455+
}
456+
)
457+
458+
return <div>{data}</div>
459+
}
460+
const { container } = render(<Page />)
461+
expect(container.textContent).toMatchInlineSnapshot(`""`)
462+
// after a rerender we should already have the cached data rendered
463+
await act(() => new Promise(res => setTimeout(res, 10)))
464+
expect(container.textContent).toMatchInlineSnapshot(`"${cachedData}"`)
465+
})
444466
})

test/use-swr.test.tsx

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,57 @@ describe('useSWR - refresh', () => {
724724
const secondContent = container.firstChild.textContent
725725
expect(firstContent).toEqual(secondContent)
726726
})
727+
728+
it('should not let the previous interval timer to set new timer if key changes too fast', async () => {
729+
function sleep(ms) {
730+
return new Promise(resolve => setTimeout(resolve, ms))
731+
}
732+
const fetcherWithToken = jest.fn(async token => {
733+
await sleep(200)
734+
return token
735+
})
736+
function Page() {
737+
const [count, setCount] = useState(0)
738+
const { data } = useSWR(count.toString(), fetcherWithToken, {
739+
refreshInterval: 100,
740+
dedupingInterval: 50
741+
})
742+
return (
743+
<button
744+
onClick={() => setCount(count + 1)}
745+
>{`click me ${data}`}</button>
746+
)
747+
}
748+
const { container } = render(<Page />)
749+
750+
// initial revalidate
751+
await act(() => sleep(200))
752+
expect(fetcherWithToken).toBeCalledTimes(1)
753+
754+
// first refresh
755+
await act(() => sleep(100))
756+
expect(fetcherWithToken).toBeCalledTimes(2)
757+
expect(fetcherWithToken).toHaveBeenLastCalledWith('0')
758+
await act(() => sleep(200))
759+
760+
// second refresh start
761+
await act(() => sleep(100))
762+
expect(fetcherWithToken).toBeCalledTimes(3)
763+
expect(fetcherWithToken).toHaveBeenLastCalledWith('0')
764+
// change the key during revalidation
765+
// The second refresh will not start a new timer
766+
fireEvent.click(container.firstElementChild)
767+
768+
// first refresh with new key 1
769+
await act(() => sleep(100))
770+
expect(fetcherWithToken).toBeCalledTimes(4)
771+
expect(fetcherWithToken).toHaveBeenLastCalledWith('1')
772+
await act(() => sleep(210))
773+
774+
// second refresh with new key 1
775+
expect(fetcherWithToken).toBeCalledTimes(5)
776+
expect(fetcherWithToken).toHaveBeenLastCalledWith('1')
777+
})
727778
})
728779

729780
describe('useSWR - revalidate', () => {
@@ -1875,15 +1926,15 @@ describe('useSWR - local mutation', () => {
18751926
})
18761927
})
18771928

1878-
describe('useSWR - context configs', () => {
1929+
describe('useSWR - configs', () => {
18791930
afterEach(cleanup)
18801931

18811932
it('should read the config fallback from the context', async () => {
18821933
let value = 0
18831934
const fetcher = () => value++
18841935

18851936
function Section() {
1886-
const { data } = useSWR('dynamic-10')
1937+
const { data } = useSWR('config-0')
18871938
return <div>data: {data}</div>
18881939
}
18891940
function Page() {
@@ -1905,6 +1956,57 @@ describe('useSWR - context configs', () => {
19051956
await act(() => new Promise(res => setTimeout(res, 110))) // update
19061957
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 1"`)
19071958
})
1959+
1960+
it('should stop revalidations when config.isPaused returns true', async () => {
1961+
const key = 'config-1'
1962+
let value = 0
1963+
const fetcher = () => {
1964+
if (value === 2) throw new Error()
1965+
return value++
1966+
}
1967+
const revalidate = () => mutate(key)
1968+
1969+
function Page() {
1970+
const [paused, setPaused] = useState(false)
1971+
const { data, error } = useSWR(key, fetcher, {
1972+
revalidateOnMount: true,
1973+
refreshInterval: 200,
1974+
isPaused() {
1975+
return paused
1976+
}
1977+
})
1978+
1979+
useEffect(() => {
1980+
// revalidate on mount and turn to idle
1981+
setPaused(true)
1982+
}, [])
1983+
1984+
return (
1985+
<div onClick={() => setPaused(!paused)}>
1986+
{error ? error : `data: ${data}`}
1987+
</div>
1988+
)
1989+
}
1990+
const { container } = render(<Page />)
1991+
1992+
await waitForDomChange({ container })
1993+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 0"`)
1994+
await act(async () => await 0)
1995+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 0"`)
1996+
revalidate()
1997+
await act(async () => await 0)
1998+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 0"`)
1999+
revalidate()
2000+
fireEvent.click(container.firstElementChild)
2001+
await act(async () => await 0)
2002+
revalidate()
2003+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 0"`)
2004+
await act(async () => await 0)
2005+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 1"`)
2006+
fireEvent.click(container.firstElementChild)
2007+
await act(async () => await new Promise(res => setTimeout(res, 400)))
2008+
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"data: 1"`)
2009+
})
19082010
})
19092011

19102012
describe('useSWR - suspense', () => {

0 commit comments

Comments
 (0)