diff --git a/src/use-swr.ts b/src/use-swr.ts index d6d21fbc6..1ab53509f 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -454,27 +454,6 @@ function useSWR( CACHE_REVALIDATORS[key].push(onUpdate) } - // set up polling - let timeout = null - if (config.refreshInterval) { - const tick = async () => { - if ( - !errorRef.current && - (config.refreshWhenHidden || isDocumentVisible()) && - (!config.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 softRevalidate() - } - - const interval = config.refreshInterval - timeout = setTimeout(tick, interval) - } - timeout = setTimeout(tick, config.refreshInterval) - } - // set up reconnecting when the browser regains network connection let reconnect = null if (config.revalidateOnReconnect) { @@ -507,15 +486,42 @@ function useSWR( } } - if (timeout !== null) { - clearTimeout(timeout) - } - if (reconnect !== null) { removeEventListener('online', reconnect) } } - }, [key, config.refreshInterval, revalidate]) + }, [key, revalidate]) + + // set up polling + useIsomorphicLayoutEffect(() => { + let timer = null + const tick = async () => { + if ( + !errorRef.current && + (config.refreshWhenHidden || isDocumentVisible()) && + (!config.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) + } + } + if (config.refreshInterval) { + timer = setTimeout(tick, config.refreshInterval) + } + return () => { + if (timer) clearTimeout(timer) + } + }, [ + config.refreshInterval, + config.refreshWhenHidden, + config.refreshWhenOffline, + revalidate + ]) // suspense if (config.suspense) { diff --git a/test/use-swr.test.tsx b/test/use-swr.test.tsx index bdb7cbdba..383f38441 100644 --- a/test/use-swr.test.tsx +++ b/test/use-swr.test.tsx @@ -334,6 +334,60 @@ describe('useSWR - refresh', () => { await act(() => new Promise(res => setTimeout(res, 200))) // update expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`) }) + + it('should update data upon interval changes', async () => { + let count = 0 + function Page() { + const [int, setInt] = React.useState(200) + const { data } = useSWR('/api', () => count++, { + refreshInterval: int, + dedupingInterval: 100 + }) + return
setInt(int + 100)}>count: {data}
+ } + const { container } = render() + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: "`) + + await waitForDomChange({ container }) // mount + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 0"`) + await act(() => { + return new Promise(res => setTimeout(res, 210)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`) + await act(() => { + return new Promise(res => setTimeout(res, 50)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`) + await act(() => { + return new Promise(res => setTimeout(res, 150)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`) + await act(() => { + fireEvent.click(container.firstElementChild) + // it will clear 200ms timer and setup a new 300ms timer + return new Promise(res => setTimeout(res, 200)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`) + await act(() => { + return new Promise(res => setTimeout(res, 110)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 3"`) + await act(() => { + // wait for new 300ms timer + return new Promise(res => setTimeout(res, 310)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 4"`) + await act(() => { + fireEvent.click(container.firstElementChild) + // it will clear 300ms timer and setup a new 400ms timer + return new Promise(res => setTimeout(res, 300)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 4"`) + await act(() => { + return new Promise(res => setTimeout(res, 110)) + }) + expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`) + }) }) describe('useSWR - revalidate', () => {