From 61dc35eb364786711b00af6541e6a10691001f7b Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Fri, 5 Mar 2021 10:58:41 +0900 Subject: [PATCH 01/18] fix: a flaky test for onLoadingSlow --- test/use-swr-config-callbacks.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/use-swr-config-callbacks.test.tsx b/test/use-swr-config-callbacks.test.tsx index a8aff594e..22a146c1c 100644 --- a/test/use-swr-config-callbacks.test.tsx +++ b/test/use-swr-config-callbacks.test.tsx @@ -160,7 +160,7 @@ describe('useSWR - config callbacks', () => { expect(state).toEqual(null) // should trigger a loading slow event - await act(() => sleep(LOADING_TIMEOUT)) + await act(() => sleep(LOADING_TIMEOUT * 1.5)) screen.getByText('hello, , a') expect(state).toEqual('a') From b4689868e754259c27eb228976f36b3a46a87351 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sun, 7 Mar 2021 01:16:48 +0900 Subject: [PATCH 02/18] fix: supprt multiple useSWRInfinite hooks in a page --- src/use-swr-infinite.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 999a7a767..a97144b29 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -55,7 +55,8 @@ function useSWRInfinite( const { initialSize = 1, - revalidateAll = false, + // should revalidate pages when a refresh timer is invoked + revalidateAll = !!config.refreshInterval, persistSize = false, ...extraConfig } = config @@ -102,10 +103,11 @@ function useSWRInfinite( // keep the data inside a ref const dataRef = useRef() + const lastFetchedDataRef = useRef() // actual swr of all pages const swr = useSWR( - firstPageKey ? ['many', firstPageKey] : null, + firstPageKey ? ['many', firstPageKey, pageCountRef.current] : null, async () => { // get the revalidate context const { originalData, force } = cache.get(contextCacheKey) || {} @@ -131,15 +133,11 @@ function useSWRInfinite( // - `revalidateAll` is enabled // - `mutate()` called // - the cache is missing - // - it's the first page and it's not the first render // - cache has changed const shouldFetchPage = revalidateAll || force || typeof pageData === 'undefined' || - (typeof force === 'undefined' && - i === 0 && - typeof dataRef.current !== 'undefined') || (originalData && !config.compare(originalData[i], pageData)) if (shouldFetchPage) { @@ -153,6 +151,7 @@ function useSWRInfinite( data.push(pageData) previousPageData = pageData + lastFetchedDataRef.current = data } // once we executed the data fetching based on the context, clear the context @@ -161,7 +160,11 @@ function useSWRInfinite( // return the data return data }, - extraConfig + // do not pass initialData for updating to revalidate + { + ...extraConfig, + initialData: didMountRef.current ? undefined : extraConfig.initialData + } ) // update dataRef @@ -198,9 +201,8 @@ function useSWRInfinite( } cache.set(pageCountCacheKey, pageCountRef.current) rerender(v => !v) - return mutate(v => v) }, - [mutate, pageCountCacheKey] + [pageCountCacheKey] ) // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object @@ -211,7 +213,9 @@ function useSWRInfinite( enumerable: true }, data: { - get: () => swr.data, + get: () => + // return the last data when revalidating + swr.data !== undefined ? swr.data : lastFetchedDataRef.current, enumerable: true }, // revalidate will be deprecated in the 1.x release From abdce59200c6b41fe4ee1228fa352f6bd19a4fb0 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 10 Mar 2021 00:45:19 +0900 Subject: [PATCH 03/18] test(useSWRInfinite): add a test for shared a page count between the same key --- test/use-swr-infinite.test.tsx | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index eadf3583f..a3dfa22f5 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -514,4 +514,62 @@ describe('useSWRInfinite', () => { await screen.findByText('page-1-1, page-1-2, page-2-1, page-2-2') expect(requests).toEqual(['/api?page=1', '/api?page=2']) }) + + it('should share data between multiple hooks have the same key', async () => { + const dummyResponses = { + '/api?page=1': ['page-1-1', 'page-1-2'], + '/api?page=2': ['page-2-1', 'page-2-2'] + } + const useCustomSWRInfinite = () => { + const { data, setSize, size } = useSWRInfinite( + index => { + return [`page-test-11`, `/api?page=${index + 1}`] + }, + async (_, index) => { + return dummyResponses[index] + } + ) + return { + data: data ? [].concat(...data) : [], + setSize, + size + } + } + + const Component = (props: { label: string }) => { + const { data, size, setSize } = useCustomSWRInfinite() + return ( + <> +
    + {data.map(value => ( +
  • + {props.label}:{value} +
  • + ))} +
+ + + ) + } + + function Page() { + return ( +
+ + +
+ ) + } + render() + + // render responses for page=1 + await screen.findByText('A:page-1-2') + await screen.findByText('B:page-1-2') + + fireEvent.click(screen.getByText('click')) + + // render responses for page=2 + await screen.findByText('A:page-2-2') + await screen.findByText('B:page-2-2') + }) }) From f19fd5c6119b82dcd8ec66d0403d44e4f925cd70 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 10 Mar 2021 01:39:38 +0900 Subject: [PATCH 04/18] fix: inconsistency for the pageCount between useSWRInfinite hooks --- src/use-swr-infinite.ts | 32 ++++++++++++++++---------------- test/use-swr-infinite.test.tsx | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index a97144b29..69436f3d8 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -1,5 +1,5 @@ // TODO: use @ts-expect-error -import { useContext, useRef, useState, useEffect, useCallback } from 'react' +import { useContext, useRef, useEffect, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' @@ -69,8 +69,6 @@ function useSWRInfinite( // not ready } - const [, rerender] = useState(false) - // we use cache to pass extra info (context) to fetcher so it can be globally shared // here we get the key of the fetcher context cache let contextCacheKey: string | null = null @@ -80,19 +78,22 @@ function useSWRInfinite( // page count is cached as well, so when navigating the list can be restored let pageCountCacheKey: string | null = null - let cachedPageSize if (firstPageKey) { pageCountCacheKey = 'size@' + firstPageKey - cachedPageSize = cache.get(pageCountCacheKey) } - const pageCountRef = useRef(cachedPageSize || initialSize) const didMountRef = useRef(false) + const resolvePageCount = useCallback( + () => cache.get(pageCountCacheKey) || initialSize, + [pageCountCacheKey, initialSize] + ) + // every time the key changes, we reset the page size if it's not persisted useEffect(() => { if (didMountRef.current) { if (!persistSize) { - pageCountRef.current = initialSize + cache.set(pageCountCacheKey, initialSize) + swr.mutate() } } else { didMountRef.current = true @@ -107,16 +108,17 @@ function useSWRInfinite( // actual swr of all pages const swr = useSWR( - firstPageKey ? ['many', firstPageKey, pageCountRef.current] : null, + firstPageKey ? ['many', firstPageKey] : null, async () => { // get the revalidate context const { originalData, force } = cache.get(contextCacheKey) || {} + const pageCount = resolvePageCount() // return an array of page data const data: Data[] = [] let previousPageData = null - for (let i = 0; i < pageCountRef.current; ++i) { + for (let i = 0; i < pageCount; ++i) { const [pageKey, pageArgs] = cache.serializeKey( getKey(i, previousPageData) ) @@ -191,22 +193,20 @@ function useSWRInfinite( ) // extend the SWR API - const size = pageCountRef.current const setSize = useCallback( arg => { if (typeof arg === 'function') { - pageCountRef.current = arg(pageCountRef.current) + cache.set(pageCountCacheKey, arg(resolvePageCount())) } else if (typeof arg === 'number') { - pageCountRef.current = arg + cache.set(pageCountCacheKey, arg) } - cache.set(pageCountCacheKey, pageCountRef.current) - rerender(v => !v) + swr.mutate() }, - [pageCountCacheKey] + [pageCountCacheKey, resolvePageCount] ) // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object - const swrInfinite = { size, setSize, mutate } + const swrInfinite = { size: resolvePageCount(), setSize, mutate } Object.defineProperties(swrInfinite, { error: { get: () => swr.error, diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index a3dfa22f5..559d313ae 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -547,7 +547,7 @@ describe('useSWRInfinite', () => { ))} - + ) } @@ -566,7 +566,7 @@ describe('useSWRInfinite', () => { await screen.findByText('A:page-1-2') await screen.findByText('B:page-1-2') - fireEvent.click(screen.getByText('click')) + fireEvent.click(screen.getByText('A:click')) // render responses for page=2 await screen.findByText('A:page-2-2') From ca4a507ca118618a5f8eb4330e82f37c16c2b232 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 10 Mar 2021 09:56:24 +0900 Subject: [PATCH 05/18] fix: keep pageCount when persistSize is true --- src/use-swr-infinite.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 69436f3d8..e1589c9f1 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -87,14 +87,16 @@ function useSWRInfinite( () => cache.get(pageCountCacheKey) || initialSize, [pageCountCacheKey, initialSize] ) + const lastPageCountRef = useRef(resolvePageCount()) // every time the key changes, we reset the page size if it's not persisted useEffect(() => { if (didMountRef.current) { - if (!persistSize) { - cache.set(pageCountCacheKey, initialSize) - swr.mutate() - } + cache.set( + pageCountCacheKey, + persistSize ? lastPageCountRef.current : initialSize + ) + swr.mutate() } else { didMountRef.current = true } @@ -197,8 +199,10 @@ function useSWRInfinite( arg => { if (typeof arg === 'function') { cache.set(pageCountCacheKey, arg(resolvePageCount())) + lastPageCountRef.current = arg(resolvePageCount()) } else if (typeof arg === 'number') { cache.set(pageCountCacheKey, arg) + lastPageCountRef.current = arg } swr.mutate() }, From 8196bc15e4c1d11cb3ef3f7bd67b71f4811dde2d Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 10 Mar 2021 10:03:08 +0900 Subject: [PATCH 06/18] refactor: use a local mutate function --- src/use-swr-infinite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index e1589c9f1..7acb5f3a7 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -204,7 +204,7 @@ function useSWRInfinite( cache.set(pageCountCacheKey, arg) lastPageCountRef.current = arg } - swr.mutate() + return mutate(v => v) }, [pageCountCacheKey, resolvePageCount] ) From 1a2d7f42b1ae6378ec47407034efa35e5c3e94cc Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Wed, 10 Mar 2021 14:48:37 +0900 Subject: [PATCH 07/18] refactor: fix a react-hooks warning --- src/use-swr-infinite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 7acb5f3a7..21470388e 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -206,7 +206,7 @@ function useSWRInfinite( } return mutate(v => v) }, - [pageCountCacheKey, resolvePageCount] + [pageCountCacheKey, resolvePageCount, mutate] ) // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object From c496d3f3a8efa401be2a578f724d504a48f020c0 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 01:03:45 +0900 Subject: [PATCH 08/18] refactor: revert unnecessary diffs --- src/use-swr-infinite.ts | 51 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 21470388e..2a4634af8 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -55,8 +55,7 @@ function useSWRInfinite( const { initialSize = 1, - // should revalidate pages when a refresh timer is invoked - revalidateAll = !!config.refreshInterval, + revalidateAll = false, persistSize = false, ...extraConfig } = config @@ -87,26 +86,13 @@ function useSWRInfinite( () => cache.get(pageCountCacheKey) || initialSize, [pageCountCacheKey, initialSize] ) + // this is used to support the persistSize option const lastPageCountRef = useRef(resolvePageCount()) - - // every time the key changes, we reset the page size if it's not persisted - useEffect(() => { - if (didMountRef.current) { - cache.set( - pageCountCacheKey, - persistSize ? lastPageCountRef.current : initialSize - ) - swr.mutate() - } else { - didMountRef.current = true - } - // initialSize isn't allowed to change during the lifecycle - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [firstPageKey]) + // this is used to return the last fetched data while revalidating + const lastFetchedDataRef = useRef() // keep the data inside a ref const dataRef = useRef() - const lastFetchedDataRef = useRef() // actual swr of all pages const swr = useSWR( @@ -137,11 +123,15 @@ function useSWRInfinite( // - `revalidateAll` is enabled // - `mutate()` called // - the cache is missing + // - it's the first page and it's not the first render // - cache has changed const shouldFetchPage = revalidateAll || force || typeof pageData === 'undefined' || + (typeof force === 'undefined' && + i === 0 && + typeof dataRef.current !== 'undefined') || (originalData && !config.compare(originalData[i], pageData)) if (shouldFetchPage) { @@ -164,11 +154,7 @@ function useSWRInfinite( // return the data return data }, - // do not pass initialData for updating to revalidate - { - ...extraConfig, - initialData: didMountRef.current ? undefined : extraConfig.initialData - } + extraConfig ) // update dataRef @@ -209,6 +195,21 @@ function useSWRInfinite( [pageCountCacheKey, resolvePageCount, mutate] ) + // every time the key changes, we reset the page size if it's not persisted + useEffect(() => { + if (!didMountRef.current) { + didMountRef.current = true + return + } + cache.set( + pageCountCacheKey, + persistSize ? lastPageCountRef.current : initialSize + ) + mutate(v => v) + // initialSize isn't allowed to change during the lifecycle + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [firstPageKey]) + // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object const swrInfinite = { size: resolvePageCount(), setSize, mutate } Object.defineProperties(swrInfinite, { @@ -217,9 +218,7 @@ function useSWRInfinite( enumerable: true }, data: { - get: () => - // return the last data when revalidating - swr.data !== undefined ? swr.data : lastFetchedDataRef.current, + get: () => swr.data, enumerable: true }, // revalidate will be deprecated in the 1.x release From 05393c8da439943d49b340c8f73efda01a78bd36 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 01:44:55 +0900 Subject: [PATCH 09/18] refactor: remove unused a ref --- src/use-swr-infinite.ts | 5 +---- src/use-swr.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 2a4634af8..3d00806b1 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -86,10 +86,8 @@ function useSWRInfinite( () => cache.get(pageCountCacheKey) || initialSize, [pageCountCacheKey, initialSize] ) - // this is used to support the persistSize option + // keep the last page count to restore it with the persistSize option const lastPageCountRef = useRef(resolvePageCount()) - // this is used to return the last fetched data while revalidating - const lastFetchedDataRef = useRef() // keep the data inside a ref const dataRef = useRef() @@ -145,7 +143,6 @@ function useSWRInfinite( data.push(pageData) previousPageData = pageData - lastFetchedDataRef.current = data } // once we executed the data fetching based on the context, clear the context diff --git a/src/use-swr.ts b/src/use-swr.ts index 4458f5f1b..f4d23e328 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -584,10 +584,7 @@ function useSWR( const softRevalidate = () => revalidate({ dedupe: true }) // trigger a revalidation - if ( - isUpdating || - willRevalidateOnMount() - ) { + if (isUpdating || willRevalidateOnMount()) { if (typeof latestKeyedData !== 'undefined' && !IS_SERVER) { // delay revalidate if there's cache // to not block the rendering From 4a554d369665ef2a145f3a7a8b5a9407739d804b Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 01:52:42 +0900 Subject: [PATCH 10/18] fix: revert the change removing a rerender with setSize --- src/use-swr-infinite.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 3d00806b1..dcaf25466 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -1,5 +1,5 @@ // TODO: use @ts-expect-error -import { useContext, useRef, useEffect, useCallback } from 'react' +import { useContext, useRef, useState, useEffect, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' @@ -68,6 +68,8 @@ function useSWRInfinite( // not ready } + const [, rerender] = useState(false) + // we use cache to pass extra info (context) to fetcher so it can be globally shared // here we get the key of the fetcher context cache let contextCacheKey: string | null = null @@ -187,6 +189,7 @@ function useSWRInfinite( cache.set(pageCountCacheKey, arg) lastPageCountRef.current = arg } + rerender(v => !v) return mutate(v => v) }, [pageCountCacheKey, resolvePageCount, mutate] From 49907e819b1c71900c260a676351d87c7cb8d660 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 22:18:57 +0900 Subject: [PATCH 11/18] fix: avoid an unnecessary re-render --- src/use-swr-infinite.ts | 31 +++++++++++++++---------------- src/use-swr.ts | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index dcaf25466..25fe3c692 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -3,7 +3,7 @@ import { useContext, useRef, useState, useEffect, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' -import useSWR from './use-swr' +import useSWR, { useIsomorphicLayoutEffect } from './use-swr' import { ValueKey, @@ -94,6 +94,20 @@ function useSWRInfinite( // keep the data inside a ref const dataRef = useRef() + // every time the key changes, we reset the page size if it's not persisted + useIsomorphicLayoutEffect(() => { + if (!didMountRef.current) { + didMountRef.current = true + return + } + cache.set( + pageCountCacheKey, + persistSize ? lastPageCountRef.current : initialSize + ) + // initialSize isn't allowed to change during the lifecycle + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [firstPageKey]) + // actual swr of all pages const swr = useSWR( firstPageKey ? ['many', firstPageKey] : null, @@ -195,21 +209,6 @@ function useSWRInfinite( [pageCountCacheKey, resolvePageCount, mutate] ) - // every time the key changes, we reset the page size if it's not persisted - useEffect(() => { - if (!didMountRef.current) { - didMountRef.current = true - return - } - cache.set( - pageCountCacheKey, - persistSize ? lastPageCountRef.current : initialSize - ) - mutate(v => v) - // initialSize isn't allowed to change during the lifecycle - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [firstPageKey]) - // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object const swrInfinite = { size: resolvePageCount(), setSize, mutate } Object.defineProperties(swrInfinite, { diff --git a/src/use-swr.ts b/src/use-swr.ts index f4d23e328..4319ee706 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -828,5 +828,5 @@ function useSWR( const SWRConfig = SWRConfigContext.Provider -export { trigger, mutate, SWRConfig } +export { trigger, mutate, SWRConfig, useIsomorphicLayoutEffect } export default useSWR From b1244dac11901bdf5ad9592201fc3e69e91cf668 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 22:43:53 +0900 Subject: [PATCH 12/18] refactor: clear variable names and more strict types --- src/use-swr-infinite.ts | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 25fe3c692..aec485ac0 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -1,5 +1,5 @@ // TODO: use @ts-expect-error -import { useContext, useRef, useState, useEffect, useCallback } from 'react' +import { useContext, useRef, useState, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' @@ -77,19 +77,19 @@ function useSWRInfinite( contextCacheKey = 'context@' + firstPageKey } - // page count is cached as well, so when navigating the list can be restored - let pageCountCacheKey: string | null = null + // page size is also cached to share the page data between hooks having the same key + let pageSizeCacheKey: string | null = null if (firstPageKey) { - pageCountCacheKey = 'size@' + firstPageKey + pageSizeCacheKey = 'size@' + firstPageKey } const didMountRef = useRef(false) - const resolvePageCount = useCallback( - () => cache.get(pageCountCacheKey) || initialSize, - [pageCountCacheKey, initialSize] - ) - // keep the last page count to restore it with the persistSize option - const lastPageCountRef = useRef(resolvePageCount()) + const resolvePageSize = useCallback((): number => { + const cachedPageSize = cache.get(pageSizeCacheKey) + return typeof cachedPageSize !== 'undefined' ? cachedPageSize : initialSize + }, [pageSizeCacheKey, initialSize]) + // keep the last page size to restore it with the persistSize option + const lastPageSizeRef = useRef(resolvePageSize()) // keep the data inside a ref const dataRef = useRef() @@ -100,9 +100,10 @@ function useSWRInfinite( didMountRef.current = true return } + // If the key has been changed, we keep the current page size if persistSize is enabled cache.set( - pageCountCacheKey, - persistSize ? lastPageCountRef.current : initialSize + pageSizeCacheKey, + persistSize ? lastPageSizeRef.current : initialSize ) // initialSize isn't allowed to change during the lifecycle // eslint-disable-next-line react-hooks/exhaustive-deps @@ -114,13 +115,13 @@ function useSWRInfinite( async () => { // get the revalidate context const { originalData, force } = cache.get(contextCacheKey) || {} - const pageCount = resolvePageCount() // return an array of page data const data: Data[] = [] + const pageSize = resolvePageSize() let previousPageData = null - for (let i = 0; i < pageCount; ++i) { + for (let i = 0; i < pageSize; ++i) { const [pageKey, pageArgs] = cache.serializeKey( getKey(i, previousPageData) ) @@ -171,7 +172,7 @@ function useSWRInfinite( ) // update dataRef - useEffect(() => { + useIsomorphicLayoutEffect(() => { dataRef.current = swr.data }, [swr.data]) @@ -195,22 +196,25 @@ function useSWRInfinite( // extend the SWR API const setSize = useCallback( - arg => { + (arg: number | ((size: number) => number)) => { + let size if (typeof arg === 'function') { - cache.set(pageCountCacheKey, arg(resolvePageCount())) - lastPageCountRef.current = arg(resolvePageCount()) + size = arg(resolvePageSize()) } else if (typeof arg === 'number') { - cache.set(pageCountCacheKey, arg) - lastPageCountRef.current = arg + size = arg + } + if (typeof size === 'number') { + cache.set(pageSizeCacheKey, size) + lastPageSizeRef.current = size } rerender(v => !v) return mutate(v => v) }, - [pageCountCacheKey, resolvePageCount, mutate] + [pageSizeCacheKey, resolvePageSize, mutate] ) // Use getter functions to avoid unnecessary re-renders caused by triggering all the getters of the returned swr object - const swrInfinite = { size: resolvePageCount(), setSize, mutate } + const swrInfinite = { size: resolvePageSize(), setSize, mutate } Object.defineProperties(swrInfinite, { error: { get: () => swr.error, From 14e3b3c8be768d943ebb7e698aecad784301b9e9 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 11 Mar 2021 23:15:16 +0900 Subject: [PATCH 13/18] refactor: put rAF and useIsomorphicLayoutEffect as one of web-presets --- src/libs/web-preset.ts | 22 +++++++++++++++++++++- src/use-swr-infinite.ts | 3 ++- src/use-swr.ts | 39 ++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/libs/web-preset.ts b/src/libs/web-preset.ts index f31d4287a..a6f288cec 100644 --- a/src/libs/web-preset.ts +++ b/src/libs/web-preset.ts @@ -1,3 +1,5 @@ +import { useEffect, useLayoutEffect } from 'react' + /** * Due to bug https://bugs.chromium.org/p/chromium/issues/detail?id=678075, * it's not reliable to detect if the browser is currently online or offline @@ -51,10 +53,28 @@ const registerOnReconnect = (cb: () => void) => { } } +const IS_SERVER = + typeof window === 'undefined' || + // @ts-ignore + !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) + +// polyfill for requestAnimationFrame +const rAF = IS_SERVER + ? null + : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) + +// React currently throws a warning when using useLayoutEffect on the server. +// To get around it, we can conditionally useEffect on the server (no-op) and +// useLayoutEffect in the browser. +const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect + export default { isOnline, isDocumentVisible, fetcher, registerOnFocus, - registerOnReconnect + registerOnReconnect, + rAF, + useIsomorphicLayoutEffect, + IS_SERVER } diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index aec485ac0..748411be3 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -3,7 +3,7 @@ import { useContext, useRef, useState, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' -import useSWR, { useIsomorphicLayoutEffect } from './use-swr' +import useSWR from './use-swr' import { ValueKey, @@ -57,6 +57,7 @@ function useSWRInfinite( initialSize = 1, revalidateAll = false, persistSize = false, + useIsomorphicLayoutEffect, ...extraConfig } = config diff --git a/src/use-swr.ts b/src/use-swr.ts index 4319ee706..526595cdb 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -2,8 +2,6 @@ import { useCallback, useContext, - useEffect, - useLayoutEffect, useState, useRef, useMemo, @@ -25,21 +23,6 @@ import { SWRConfiguration } from './types' -const IS_SERVER = - typeof window === 'undefined' || - // @ts-ignore - !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) - -// polyfill for requestAnimationFrame -const rAF = IS_SERVER - ? null - : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) - -// React currently throws a warning when using useLayoutEffect on the server. -// To get around it, we can conditionally useEffect on the server (no-op) and -// useLayoutEffect in the browser. -const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect - type Revalidator = (...args: any[]) => void // global state managers @@ -57,22 +40,30 @@ const now = (() => { return () => ++ts })() +const { + isOnline, + isDocumentVisible, + registerOnFocus, + registerOnReconnect, + IS_SERVER +} = defaultConfig + // setup DOM events listeners for `focus` and `reconnect` actions if (!IS_SERVER) { const revalidate = (revalidators: Record) => { - if (!defaultConfig.isDocumentVisible() || !defaultConfig.isOnline()) return + if (!isDocumentVisible() || !isOnline()) return for (const key in revalidators) { if (revalidators[key][0]) revalidators[key][0]() } } - if (typeof defaultConfig.registerOnFocus === 'function') { - defaultConfig.registerOnFocus(() => revalidate(FOCUS_REVALIDATORS)) + if (typeof registerOnFocus === 'function') { + registerOnFocus(() => revalidate(FOCUS_REVALIDATORS)) } - if (typeof defaultConfig.registerOnReconnect === 'function') { - defaultConfig.registerOnReconnect(() => revalidate(RECONNECT_REVALIDATORS)) + if (typeof registerOnReconnect === 'function') { + registerOnReconnect(() => revalidate(RECONNECT_REVALIDATORS)) } } @@ -229,6 +220,8 @@ function useSWR( : {} ) + const { rAF, useIsomorphicLayoutEffect } = config + // in typescript args.length > 2 is not same as args.lenth === 3 // we do a safe type assertion here // args.length === 3 @@ -828,5 +821,5 @@ function useSWR( const SWRConfig = SWRConfigContext.Provider -export { trigger, mutate, SWRConfig, useIsomorphicLayoutEffect } +export { trigger, mutate, SWRConfig } export default useSWR From 1e4bbbeb897470a31f85a8585510d7bcb6386914 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sat, 13 Mar 2021 18:36:04 +0900 Subject: [PATCH 14/18] Revert "refactor: put rAF and useIsomorphicLayoutEffect as one of web-presets" This reverts commit 2bc4ac202804519701234fec366ddda18ee656c6. --- src/libs/web-preset.ts | 22 +--------------------- src/use-swr-infinite.ts | 3 +-- src/use-swr.ts | 39 +++++++++++++++++++++++---------------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/libs/web-preset.ts b/src/libs/web-preset.ts index a6f288cec..f31d4287a 100644 --- a/src/libs/web-preset.ts +++ b/src/libs/web-preset.ts @@ -1,5 +1,3 @@ -import { useEffect, useLayoutEffect } from 'react' - /** * Due to bug https://bugs.chromium.org/p/chromium/issues/detail?id=678075, * it's not reliable to detect if the browser is currently online or offline @@ -53,28 +51,10 @@ const registerOnReconnect = (cb: () => void) => { } } -const IS_SERVER = - typeof window === 'undefined' || - // @ts-ignore - !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) - -// polyfill for requestAnimationFrame -const rAF = IS_SERVER - ? null - : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) - -// React currently throws a warning when using useLayoutEffect on the server. -// To get around it, we can conditionally useEffect on the server (no-op) and -// useLayoutEffect in the browser. -const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect - export default { isOnline, isDocumentVisible, fetcher, registerOnFocus, - registerOnReconnect, - rAF, - useIsomorphicLayoutEffect, - IS_SERVER + registerOnReconnect } diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 748411be3..aec485ac0 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -3,7 +3,7 @@ import { useContext, useRef, useState, useCallback } from 'react' import defaultConfig, { cache } from './config' import SWRConfigContext from './swr-config-context' -import useSWR from './use-swr' +import useSWR, { useIsomorphicLayoutEffect } from './use-swr' import { ValueKey, @@ -57,7 +57,6 @@ function useSWRInfinite( initialSize = 1, revalidateAll = false, persistSize = false, - useIsomorphicLayoutEffect, ...extraConfig } = config diff --git a/src/use-swr.ts b/src/use-swr.ts index 526595cdb..4319ee706 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -2,6 +2,8 @@ import { useCallback, useContext, + useEffect, + useLayoutEffect, useState, useRef, useMemo, @@ -23,6 +25,21 @@ import { SWRConfiguration } from './types' +const IS_SERVER = + typeof window === 'undefined' || + // @ts-ignore + !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) + +// polyfill for requestAnimationFrame +const rAF = IS_SERVER + ? null + : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) + +// React currently throws a warning when using useLayoutEffect on the server. +// To get around it, we can conditionally useEffect on the server (no-op) and +// useLayoutEffect in the browser. +const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect + type Revalidator = (...args: any[]) => void // global state managers @@ -40,30 +57,22 @@ const now = (() => { return () => ++ts })() -const { - isOnline, - isDocumentVisible, - registerOnFocus, - registerOnReconnect, - IS_SERVER -} = defaultConfig - // setup DOM events listeners for `focus` and `reconnect` actions if (!IS_SERVER) { const revalidate = (revalidators: Record) => { - if (!isDocumentVisible() || !isOnline()) return + if (!defaultConfig.isDocumentVisible() || !defaultConfig.isOnline()) return for (const key in revalidators) { if (revalidators[key][0]) revalidators[key][0]() } } - if (typeof registerOnFocus === 'function') { - registerOnFocus(() => revalidate(FOCUS_REVALIDATORS)) + if (typeof defaultConfig.registerOnFocus === 'function') { + defaultConfig.registerOnFocus(() => revalidate(FOCUS_REVALIDATORS)) } - if (typeof registerOnReconnect === 'function') { - registerOnReconnect(() => revalidate(RECONNECT_REVALIDATORS)) + if (typeof defaultConfig.registerOnReconnect === 'function') { + defaultConfig.registerOnReconnect(() => revalidate(RECONNECT_REVALIDATORS)) } } @@ -220,8 +229,6 @@ function useSWR( : {} ) - const { rAF, useIsomorphicLayoutEffect } = config - // in typescript args.length > 2 is not same as args.lenth === 3 // we do a safe type assertion here // args.length === 3 @@ -821,5 +828,5 @@ function useSWR( const SWRConfig = SWRConfigContext.Provider -export { trigger, mutate, SWRConfig } +export { trigger, mutate, SWRConfig, useIsomorphicLayoutEffect } export default useSWR From b4dfc0653643800135d66f0646cca22f5ad1bc1e Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sat, 13 Mar 2021 18:46:20 +0900 Subject: [PATCH 15/18] refactor: create env.ts for a browser environment --- src/env.ts | 16 ++++++++++++++++ src/use-swr-infinite.ts | 3 ++- src/use-swr.ts | 18 +----------------- 3 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 src/env.ts diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 000000000..d1284c2a9 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,16 @@ +import { useEffect, useLayoutEffect } from 'react' + +export const IS_SERVER = + typeof window === 'undefined' || + // @ts-ignore + !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) + +// polyfill for requestAnimationFrame +export const rAF = IS_SERVER + ? null + : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) + +// React currently throws a warning when using useLayoutEffect on the server. +// To get around it, we can conditionally useEffect on the server (no-op) and +// useLayoutEffect in the browser. +export const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index aec485ac0..9666339c5 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -2,8 +2,9 @@ import { useContext, useRef, useState, useCallback } from 'react' import defaultConfig, { cache } from './config' +import { useIsomorphicLayoutEffect } from './env' import SWRConfigContext from './swr-config-context' -import useSWR, { useIsomorphicLayoutEffect } from './use-swr' +import useSWR from './use-swr' import { ValueKey, diff --git a/src/use-swr.ts b/src/use-swr.ts index 4319ee706..37a2a3296 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -2,8 +2,6 @@ import { useCallback, useContext, - useEffect, - useLayoutEffect, useState, useRef, useMemo, @@ -11,6 +9,7 @@ import { } from 'react' import defaultConfig, { cache } from './config' +import { IS_SERVER, rAF, useIsomorphicLayoutEffect } from './env' import SWRConfigContext from './swr-config-context' import { Action, @@ -25,21 +24,6 @@ import { SWRConfiguration } from './types' -const IS_SERVER = - typeof window === 'undefined' || - // @ts-ignore - !!(typeof Deno !== 'undefined' && Deno && Deno.version && Deno.version.deno) - -// polyfill for requestAnimationFrame -const rAF = IS_SERVER - ? null - : window['requestAnimationFrame'] || (f => setTimeout(f, 1)) - -// React currently throws a warning when using useLayoutEffect on the server. -// To get around it, we can conditionally useEffect on the server (no-op) and -// useLayoutEffect in the browser. -const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect - type Revalidator = (...args: any[]) => void // global state managers From 47ca8bc65fc0ef0250cc0a47ef3dabe8454bd54f Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sat, 13 Mar 2021 19:57:31 +0900 Subject: [PATCH 16/18] refactor: remove an ununsed export --- src/use-swr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/use-swr.ts b/src/use-swr.ts index 37a2a3296..0d3d6602d 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -812,5 +812,5 @@ function useSWR( const SWRConfig = SWRConfigContext.Provider -export { trigger, mutate, SWRConfig, useIsomorphicLayoutEffect } +export { trigger, mutate, SWRConfig } export default useSWR From 25d9016bba2a8f7584b4940b8cfd6da5dd9cf2fc Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sat, 13 Mar 2021 20:07:10 +0900 Subject: [PATCH 17/18] refactor: remove unnecessary diffs --- src/use-swr-infinite.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/use-swr-infinite.ts b/src/use-swr-infinite.ts index 9666339c5..45c174b29 100644 --- a/src/use-swr-infinite.ts +++ b/src/use-swr-infinite.ts @@ -92,9 +92,6 @@ function useSWRInfinite( // keep the last page size to restore it with the persistSize option const lastPageSizeRef = useRef(resolvePageSize()) - // keep the data inside a ref - const dataRef = useRef() - // every time the key changes, we reset the page size if it's not persisted useIsomorphicLayoutEffect(() => { if (!didMountRef.current) { @@ -110,6 +107,9 @@ function useSWRInfinite( // eslint-disable-next-line react-hooks/exhaustive-deps }, [firstPageKey]) + // keep the data inside a ref + const dataRef = useRef() + // actual swr of all pages const swr = useSWR( firstPageKey ? ['many', firstPageKey] : null, From ff73c198b5624f7a6ec9b56f0f343acc9e2ecd59 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sat, 13 Mar 2021 20:11:35 +0900 Subject: [PATCH 18/18] test: make a test using setInterval more reliable --- test/use-swr-configs.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/use-swr-configs.test.tsx b/test/use-swr-configs.test.tsx index aa8e78a64..d14e617fa 100644 --- a/test/use-swr-configs.test.tsx +++ b/test/use-swr-configs.test.tsx @@ -6,7 +6,7 @@ import { sleep } from './utils' describe('useSWR - configs', () => { it('should read the config fallback from the context', async () => { let value = 0 - const INTERVAL = 10 + const INTERVAL = 100 const fetcher = () => value++ function Section() {