From b21ca4f4c5cbd2f05d645a71b96c4d7ec018474e Mon Sep 17 00:00:00 2001 From: Yixuan Xu Date: Wed, 16 Sep 2020 01:32:50 +0800 Subject: [PATCH] try fix cannot stop polling by setting refreshInterval (#632) (#638) * fix Cannot stop polling by setting refreshInterval (#632) * clear exiting the effect when key changes * add test case for a-b-a and try fix a-b-a * set less task --- src/use-swr.ts | 16 +++--- test/use-swr.test.tsx | 120 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/use-swr.ts b/src/use-swr.ts index 2ad18db79..c08955840 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -260,7 +260,7 @@ function useSWR( let shouldUpdateState = false for (let k in payload) { if (stateRef.current[k] === payload[k]) { - continue; + continue } stateRef.current[k] = payload[k] @@ -584,26 +584,26 @@ function useSWR( } }, [key, revalidate]) - // set up polling useIsomorphicLayoutEffect(() => { let timer = null const tick = async () => { if ( !stateRef.current.error && - (config.refreshWhenHidden || isDocumentVisible()) && - (config.refreshWhenOffline || isOnline()) + (configRef.current.refreshWhenHidden || isDocumentVisible()) && + (configRef.current.refreshWhenOffline || isOnline()) ) { // only revalidate when the page is visible // if API request errored, we stop polling in this round // and let the error retry function handle it await revalidate({ dedupe: true }) } - if (config.refreshInterval) { - timer = setTimeout(tick, config.refreshInterval) + // Read the latest refreshInterval + if (configRef.current.refreshInterval && !stateRef.current.error) { + timer = setTimeout(tick, configRef.current.refreshInterval) } } - if (config.refreshInterval) { - timer = setTimeout(tick, config.refreshInterval) + if (configRef.current.refreshInterval) { + timer = setTimeout(tick, configRef.current.refreshInterval) } return () => { if (timer) clearTimeout(timer) diff --git a/test/use-swr.test.tsx b/test/use-swr.test.tsx index a884e8cd2..0339687ff 100644 --- a/test/use-swr.test.tsx +++ b/test/use-swr.test.tsx @@ -510,6 +510,110 @@ describe('useSWR - refresh', () => { expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`) }) + it('should update data upon interval changes -- changes happened during revalidate', async () => { + let count = 0 + const STOP_POLLING_THRESHOLD = 2 + function Page() { + const [flag, setFlag] = useState(0) + const shouldPoll = flag < STOP_POLLING_THRESHOLD + const { data } = useSWR( + '/interval-changes-during-revalidate', + () => count++, + { + refreshInterval: shouldPoll ? 200 : 0, + dedupingInterval: 100, + onSuccess() { + setFlag(value => value + 1) + } + } + ) + return ( +
setFlag(0)}> + count: {data} {flag} +
+ ) + } + const { container } = render() + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 0"` + ) + + await waitForDomChange({ container }) // mount + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 0 1"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 1 2"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 1 2"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 1 2"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 1 2"` + ) + + await act(() => { + fireEvent.click(container.firstElementChild) + // it will setup a new 200ms timer + return new Promise(res => setTimeout(res, 100)) + }) + + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 1 0"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 100)) + }) + + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 2 1"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 3 2"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 3 2"` + ) + + await act(() => { + return new Promise(res => setTimeout(res, 200)) + }) + + expect(container.firstChild.textContent).toMatchInlineSnapshot( + `"count: 3 2"` + ) + }) + it('should allow use custom isEqual method', async () => { function Page() { const { data, revalidate } = useSWR( @@ -1920,9 +2024,9 @@ describe('useSWR - key', () => { }) it('should revalidate if a function key changes identity', async () => { - const closureFunctions: {[key: string]: () => Promise} = {} + const closureFunctions: { [key: string]: () => Promise } = {} - const closureFactory = (id) => { + const closureFactory = id => { if (closureFunctions[id]) return closureFunctions[id] closureFunctions[id] = () => Promise.resolve(`data-${id}`) return closureFunctions[id] @@ -1938,24 +2042,20 @@ describe('useSWR - key', () => { const fnWithClosure = closureFactory(id) const { data } = useSWR([fnWithClosure], fetcher) - return ( -
- {data} -
- ) + return
{data}
} - const { container } = render(); + const { container } = render() const closureSpy = jest.spyOn(closureFunctions, 'first') await waitForDomChange({ container }) expect(container.firstChild.textContent).toMatchInlineSnapshot( `"data-first"` ) expect(closureSpy).toHaveBeenCalledTimes(1) - + // update, but don't change the id. // Function identity should stay the same, and useSWR should not call the function again. - await act(() => updateId('first')); + await act(() => updateId('first')) expect(container.firstChild.textContent).toMatchInlineSnapshot( `"data-first"` )