From 6417c84bac1b325ecb7821f04586bad0bd288e8a Mon Sep 17 00:00:00 2001 From: Alexandre Philibeaux Date: Sun, 21 Dec 2025 22:36:24 +0100 Subject: [PATCH] feat: enhance loading states in data loader hooks Signed-off-by: Alexandre Philibeaux --- .changeset/fresh-colts-stay.md | 9 ++ .gitignore | 6 +- .../cookies-consent/CookieConsentProvider.tsx | 2 +- packages/use-dataloader/README.md | 26 ++++-- .../src/__tests__/useDataLoader.test.tsx | 91 +++++++++++++++--- .../__tests__/useInfiniteDataLoader.test.tsx | 93 +++++++++++++++++-- packages/use-dataloader/src/types.ts | 9 +- packages/use-dataloader/src/useDataLoader.ts | 47 ++++++++-- .../src/useInfiniteDataLoader.ts | 31 +++++-- 9 files changed, 264 insertions(+), 50 deletions(-) create mode 100644 .changeset/fresh-colts-stay.md diff --git a/.changeset/fresh-colts-stay.md b/.changeset/fresh-colts-stay.md new file mode 100644 index 000000000..1923cf7fb --- /dev/null +++ b/.changeset/fresh-colts-stay.md @@ -0,0 +1,9 @@ +--- +"@scaleway/use-dataloader": major +--- + +- Changed `isLoading` behavior to be `true` only during the initial fetch when there's no cached data +- `isLoading` is now `false` during subsequent fetches, even when `isFetching` is `true` +- Added distinction between initial loading (`isLoading`) and ongoing fetching (`isFetching`) +- `isLoading` is `true` only when there is no cache data and we're fetching for the first time +- `isFetching` remains `true` during any active request (initial or subsequent) diff --git a/.gitignore b/.gitignore index 2a09aaa78..b388ec739 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,8 @@ vitest.config.ts.* # Yalc yalc.lock -.yalc/ \ No newline at end of file +.yalc/ + +# ai +.ai +AGENTS.md diff --git a/packages/use-analytics/src/cookies-consent/CookieConsentProvider.tsx b/packages/use-analytics/src/cookies-consent/CookieConsentProvider.tsx index dbb652b3d..1cfaba364 100644 --- a/packages/use-analytics/src/cookies-consent/CookieConsentProvider.tsx +++ b/packages/use-analytics/src/cookies-consent/CookieConsentProvider.tsx @@ -14,8 +14,8 @@ import { CATEGORIES, CONSENT_ADVERTISING_MAX_AGE, CONSENT_MAX_AGE, - COOKIES_OPTIONS, COOKIE_PREFIX, + COOKIES_OPTIONS, HASH_COOKIE, } from '../constants' import { uniq } from '../helpers/array' diff --git a/packages/use-dataloader/README.md b/packages/use-dataloader/README.md index f0311e21f..b6a1ca4a4 100644 --- a/packages/use-dataloader/README.md +++ b/packages/use-dataloader/README.md @@ -118,19 +118,19 @@ const fakePromise = () => function MyComponent() { // Use a key if you want to persist data in the DataLoaderProvider cache - const { data, isLoading, isSuccess, isError, error } = useDataLoader( + const { data, isLoading, isFetching, isSuccess, isError, error } = useDataLoader( 'cache-key', fakePromise, ) // This is the first time we load the data if (isLoading && !data) { - return
Loading...
+ return
Loading initial data...
} - // This happen when you already load the data but want to reload it - if (isLoading && data) { - return
Reloading...
+ // This happen when you already loaded the data but want to reload it + if (isFetching && data) { + return
Refreshing...
} // Will be true when the promise is resolved @@ -162,14 +162,19 @@ const fakePromise = () => function MyComponentThatUseDataLoader({key}) { // Use a key if you want to persist data in the DataLoaderProvider cache - const { data, isLoading, isSuccess, isError, error } = useDataLoader( + const { data, isLoading, isFetching, isSuccess, isError, error } = useDataLoader( key, fakePromise, ) - // Will be true during the promise + // Will be true during the initial load if (isLoading) { - return
Loading...
+ return
Loading initial data...
+ } + + // Will be true during any active request (initial or subsequent) + if (isFetching) { + return
Refreshing...
} // Will be true when the promise is resolved @@ -236,10 +241,13 @@ const useDataLoader = ( ) ``` +The hook returns an object with the following properties: + | Property | Description | | :----------: | :------------------------------------------------------------------------------------------------------------------------------------------: | | isIdle | `true` if the request is not launched | -| isLoading | `true` if the request is launched **or** enabled is `true` and isIdle is `true` | +| isLoading | `true` only during the initial fetch when there's no cached data | +| isFetching | `true` when there is an active request in progress (initial or subsequent) | | isSuccess | `true`if the request finished successfully | | isError | `true` if the request throw an error | | isPolling | `true` if the request if `enabled` is true, `pollingInterval` is defined and the status is `isLoading`,`isSuccess` or during the first fetch | diff --git a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx index e71189b5f..b3e4c3b30 100644 --- a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx +++ b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx @@ -255,7 +255,7 @@ describe('useDataLoader', () => { expect(result.current.isLoading).toBe(false) result.current.reload().catch(() => null) result.current.reload().catch(() => null) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(result.current.data).toBe(true) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toBe(true) @@ -324,20 +324,21 @@ describe('useDataLoader', () => { ) expect(result.current.data).toBe(undefined) expect(result.current.isPolling).toBe(true) - expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) expect(pollingProps.method).toBeCalledTimes(1) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toBe(true) expect(result.current.isSuccess).toBe(true) expect(result.current.isPolling).toBe(true) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(pollingProps.method).toBeCalledTimes(2) expect(result.current.isPolling).toBe(true) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toBe(true) + expect(result.current.isPolling).toBe(true) expect(result.current.isSuccess).toBe(true) - expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) rerender({ ...pollingProps, config: { @@ -348,13 +349,13 @@ describe('useDataLoader', () => { expect(result.current.data).toBe(true) expect(result.current.isPolling).toBe(true) expect(result.current.isSuccess).toBe(true) - expect(result.current.isLoading).toBe(false) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + expect(result.current.isFetching).toBe(false) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(result.current.isSuccess).toBe(false) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(method2).toBeCalledTimes(1) - expect(result.current.isPolling).toBe(true) - expect(result.current.isLoading).toBe(false) + expect(result.current.isSuccess).toBe(true) + expect(result.current.isFetching).toBe(false) expect(result.current.data).toBe(2) rerender({ @@ -364,7 +365,7 @@ describe('useDataLoader', () => { }, method: method2, }) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(result.current.data).toBe(2) expect(result.current.isPolling).toBe(true) expect(result.current.isSuccess).toBe(false) @@ -752,7 +753,7 @@ describe('useDataLoader', () => { expect(result.current[0]?.data).toBe(true) result.current[1]?.reload().catch(() => null) - await waitFor(() => expect(result.current[1]?.isLoading).toBe(true)) + await waitFor(() => expect(result.current[1]?.isFetching).toBe(true)) expect(result.current[1]?.data).toBe(true) await waitFor(() => expect(result.current[1]?.isSuccess).toBe(true)) @@ -801,7 +802,7 @@ describe('useDataLoader', () => { expect(mockedFn).toBeCalledTimes(1) result.current[1].reloadAll().catch(() => null) - await waitFor(() => expect(result.current[0].isLoading).toBe(true)) + await waitFor(() => expect(result.current[0].isFetching).toBe(true)) expect(result.current[0].data).toBe(true) expect(Object.values(result.current[1].getReloads()).length).toBe(1) @@ -883,8 +884,8 @@ describe('useDataLoader', () => { await waitFor(() => expect(result.current[0]?.isSuccess).toBe(true)) testingProps.config2.enabled = true rerender(testingProps) - await waitFor(() => expect(result.current[0]?.isLoading).toBe(true)) - await waitFor(() => expect(result.current[1]?.isLoading).toBe(true)) + await waitFor(() => expect(result.current[0]?.isFetching).toBe(true)) + await waitFor(() => expect(result.current[1]?.isFetching).toBe(true)) expect(testingProps.method).toBeCalledTimes(2) expect(result.current[0]?.data).toBe(true) expect(result.current[0]?.previousData).toBe(undefined) @@ -893,4 +894,68 @@ describe('useDataLoader', () => { await waitFor(() => expect(result.current[0]?.isSuccess).toBe(true)) await waitFor(() => expect(result.current[1]?.isSuccess).toBe(true)) }) + + test('should differentiate between isLoading and isFetching', async () => { + let resolveIt = false + const method = vi.fn(() => { + const promiseFn = () => + new Promise(resolve => { + setInterval(() => { + if (resolveIt) { + resolve({ id: 1, name: 'test' }) + } + }, PROMISE_TIMEOUT) + }) + + return promiseFn() + }) + + const testProps = { + config: { + enabled: true, + }, + key: 'test-isLoading-vs-isFetching', + method, + } + + const { result } = renderHook( + props => useDataLoader(props.key, props.method, props.config), + { + initialProps: testProps, + wrapper, + }, + ) + + // Initially, isLoading should be true (first load with no cache) + expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) + expect(result.current.data).toBe(undefined) + + // Resolve the first request + resolveIt = true + await waitFor(() => expect(result.current.isSuccess).toBe(true)) + + // After first load, both should be false + expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) + expect(result.current.data).toEqual({ id: 1, name: 'test' }) + + // Trigger a reload + resolveIt = false + result.current.reload().catch(() => null) + + // During reload, isLoading should be false (we have cached data) but isFetching should be true + await waitFor(() => expect(result.current.isFetching).toBe(true)) + expect(result.current.isLoading).toBe(false) + expect(result.current.data).toEqual({ id: 1, name: 'test' }) // Still have cached data + + // Resolve the reload + resolveIt = true + await waitFor(() => expect(result.current.isSuccess).toBe(true)) + + // After reload, both should be false again + expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) + expect(result.current.data).toEqual({ id: 1, name: 'test' }) + }) }) diff --git a/packages/use-dataloader/src/__tests__/useInfiniteDataLoader.test.tsx b/packages/use-dataloader/src/__tests__/useInfiniteDataLoader.test.tsx index 9e785b3b1..b9313d34d 100644 --- a/packages/use-dataloader/src/__tests__/useInfiniteDataLoader.test.tsx +++ b/packages/use-dataloader/src/__tests__/useInfiniteDataLoader.test.tsx @@ -31,6 +31,7 @@ const getPrerequisite = (key: string) => { }, 100) } } + resolvePromise() }), ) @@ -79,6 +80,7 @@ describe('useInfinitDataLoader', () => { ) expect(result.current.data).toBe(undefined) expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) expect(initialProps.method).toHaveBeenCalledTimes(1) setCanResolve(true) await waitFor(() => expect(result.current.isSuccess).toBe(true)) @@ -87,6 +89,8 @@ describe('useInfinitDataLoader', () => { { nextPage: 2, data: 'Page 1 data' }, ]) expect(result.current.isLoading).toBe(false) + expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) }) it('should get the first and loadMore one page on mount while enabled', async () => { @@ -107,6 +111,8 @@ describe('useInfinitDataLoader', () => { ) expect(result.current.data).toBe(undefined) expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) + expect(result.current.isFetching).toBe(true) expect(initialProps.method).toHaveBeenCalledTimes(1) expect(initialProps.method).toHaveBeenCalledWith({ page: 1, @@ -118,6 +124,7 @@ describe('useInfinitDataLoader', () => { { nextPage: 2, data: 'Page 1 data' }, ]) expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) setCanResolve(false) act(() => { result.current.loadMore() @@ -125,13 +132,15 @@ describe('useInfinitDataLoader', () => { expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, ]) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(initialProps.method).toHaveBeenCalledTimes(2) expect(initialProps.method).toHaveBeenCalledWith({ page: 2, }) setCanResolve(true) + await waitFor(() => expect(result.current.isFetching).toBe(true)) await waitFor(() => expect(result.current.isSuccess).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(false)) expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, { nextPage: 3, data: 'Page 2 data' }, @@ -157,6 +166,7 @@ describe('useInfinitDataLoader', () => { ) expect(result.current.data).toBe(undefined) expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) expect(initialProps.method).toHaveBeenCalledTimes(1) expect(initialProps.method).toHaveBeenCalledWith({ page: 1, @@ -169,10 +179,11 @@ describe('useInfinitDataLoader', () => { { nextPage: 2, data: 'Page 1 data' }, ]) expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) act(() => { result.current.loadMore() }) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, ]) @@ -191,7 +202,7 @@ describe('useInfinitDataLoader', () => { act(() => { result.current.reload().catch(() => null) }) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) setCanResolve(true) await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toStrictEqual([ @@ -226,14 +237,16 @@ describe('useInfinitDataLoader', () => { ) expect(result.current.data).toBe(undefined) expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) expect(initialProps.method).toHaveBeenCalledTimes(0) rerender(localInitialProps) expect(result.current.data).toBe(undefined) expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) expect(initialProps.method).toHaveBeenCalledTimes(0) rerender({ ...localInitialProps, config: { ...config, enabled: true } }) expect(result.current.data).toBe(undefined) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(initialProps.method).toHaveBeenCalledTimes(1) expect(initialProps.method).toHaveBeenCalledWith({ page: 1, @@ -246,10 +259,11 @@ describe('useInfinitDataLoader', () => { { nextPage: 2, data: 'Page 1 data' }, ]) expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) act(() => { result.current.loadMore() }) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, ]) @@ -258,7 +272,12 @@ describe('useInfinitDataLoader', () => { page: 2, }) setCanResolve(true) + await waitFor(() => expect(result.current.isFetching).toBe(true)) await waitFor(() => expect(result.current.isSuccess).toBe(true)) + await waitFor(() => expect(result.current.isLoading).toBe(false)) + // After loadMore completes, we should still have only 2 calls + // (initial load + loadMore), not 3, because reload reuses existing requests + expect(initialProps.method).toHaveBeenCalledTimes(2) expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, { nextPage: 3, data: 'Page 2 data' }, @@ -268,9 +287,71 @@ describe('useInfinitDataLoader', () => { act(() => { result.current.reload().catch(() => null) }) - await waitFor(() => expect(result.current.isLoading).toBe(true)) + await waitFor(() => expect(result.current.isFetching).toBe(true)) + setCanResolve(true) + await waitFor(() => expect(result.current.isFetching).toBe(false)) + expect(result.current.data).toStrictEqual([ + { nextPage: 2, data: 'Page 1 data' }, + { nextPage: 3, data: 'Page 2 data' }, + ]) + }) + + it('should differentiate between isLoading and isFetching', async () => { + const { setCanResolve, initialProps } = getPrerequisite( + 'test-isLoading-vs-isFetching', + ) + + const { result } = renderHook( + props => + useInfiniteDataLoader( + props.key, + props.method, + props.baseParams, + 'page', + config, + ), + { + initialProps, + wrapper, + }, + ) + + // Initially, isLoading should be true (first load with no cache) + expect(result.current.isLoading).toBe(true) + expect(result.current.isFetching).toBe(true) + expect(result.current.data).toBe(undefined) + + // Resolve the first request + setCanResolve(true) + await waitFor(() => expect(result.current.isSuccess).toBe(true)) + + // After first load, isLoading should be false but isFetching should also be false + expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) + expect(result.current.data).toStrictEqual([ + { nextPage: 2, data: 'Page 1 data' }, + ]) + + // Trigger a loadMore + setCanResolve(false) + act(() => { + result.current.loadMore() + }) + + // During loadMore, isLoading should be false (we have cached data) but isFetching should be true + await waitFor(() => expect(result.current.isFetching).toBe(true)) + expect(result.current.isLoading).toBe(false) + expect(result.current.data).toStrictEqual([ + { nextPage: 2, data: 'Page 1 data' }, + ]) // Still have cached data + + // Resolve the loadMore setCanResolve(true) await waitFor(() => expect(result.current.isSuccess).toBe(true)) + + // After loadMore, both should be false again + expect(result.current.isLoading).toBe(false) + expect(result.current.isFetching).toBe(false) expect(result.current.data).toStrictEqual([ { nextPage: 2, data: 'Page 1 data' }, { nextPage: 3, data: 'Page 2 data' }, diff --git a/packages/use-dataloader/src/types.ts b/packages/use-dataloader/src/types.ts index a1dca905f..c0378cf1a 100644 --- a/packages/use-dataloader/src/types.ts +++ b/packages/use-dataloader/src/types.ts @@ -73,9 +73,13 @@ export type UseDataLoaderResult = { */ isIdle: boolean /** - * True if the request is launched + * True only when there is no cache data and we're fetching data for the first time */ isLoading: boolean + /** + * True if there is an active request in progress + */ + isFetching: boolean /** * True if the request if enabled is true, pollingInterval is defined and the status is isLoading or isSuccess */ @@ -119,9 +123,10 @@ export type UseInfiniteDataLoaderResult = { isError: boolean isIdle: boolean isLoading: boolean - isLoadingFirstPage: boolean + isFetching: boolean isSuccess: boolean hasNextPage: boolean + isLoadingFirstPage: boolean reload: () => Promise loadMore: () => void } diff --git a/packages/use-dataloader/src/useDataLoader.ts b/packages/use-dataloader/src/useDataLoader.ts index 49ccd7e7c..bd16efb09 100644 --- a/packages/use-dataloader/src/useDataLoader.ts +++ b/packages/use-dataloader/src/useDataLoader.ts @@ -71,7 +71,17 @@ export const useDataLoader = ( const previousDataRef = useRef(request.data) + // Compute the data that will be returned to the user + const computedData = !request.isFirstLoading ? request.data : initialData + + // isLoading is true only when there is no cache data and we're fetching data for the first time const isLoading = + !computedData && + request.isFirstLoading && + (request.status === StatusEnum.LOADING || optimisticIsLoadingRef.current) + + // isFetching is true when there is an active request in progress + const isFetching = request.status === StatusEnum.LOADING || optimisticIsLoadingRef.current const isSuccess = request.status === StatusEnum.SUCCESS @@ -87,11 +97,26 @@ export const useDataLoader = ( (typeof needPolling !== 'function' && needPolling)) ) - const reload = useCallback( - () => - request.load(true).then(onSuccessRef.current).catch(onErrorRef.current), - [request], - ) + const reload: () => Promise = useCallback(async () => { + // Set optimistic loading state to true when reload is called + optimisticIsLoadingRef.current = true + + const onSuccessHandler = (result: ResultType) => { + // Set optimistic loading state to false when request completes + optimisticIsLoadingRef.current = false + + return onSuccessRef.current?.(result) + } + + const onErrorHandler = (error: ErrorType & Error) => { + // Set optimistic loading state to false when request completes + optimisticIsLoadingRef.current = false + + return onErrorRef.current?.(error) + } + + return request.load(true).then(onSuccessHandler).catch(onErrorHandler) + }, [request]) useEffect(() => { needPollingRef.current = needPolling @@ -117,12 +142,17 @@ export const useDataLoader = ( useEffect(() => { if (needLoad) { - const defaultOnSuccessOrError = () => {} + // Set optimistic loading state to true when auto-loading is triggered + optimisticIsLoadingRef.current = true + + const defaultOnSuccessOrError = () => { + // Set optimistic loading state to false when request completes + optimisticIsLoadingRef.current = false + } const onSuccessLoad = onSuccessRef.current ?? defaultOnSuccessOrError const onFailedLoad = onErrorRef.current ?? defaultOnSuccessOrError request.load().then(onSuccessLoad).catch(onFailedLoad) } - optimisticIsLoadingRef.current = false }, [needLoad, request]) useEffect(() => { @@ -155,9 +185,10 @@ export const useDataLoader = ( }, [pollingInterval, request]) return { - data: !request.isFirstLoading ? request.data : initialData, + data: computedData, error: request.error, isError, + isFetching, isIdle, isLoading, isPolling, diff --git a/packages/use-dataloader/src/useInfiniteDataLoader.ts b/packages/use-dataloader/src/useInfiniteDataLoader.ts index bac496dbc..214cf542e 100644 --- a/packages/use-dataloader/src/useInfiniteDataLoader.ts +++ b/packages/use-dataloader/src/useInfiniteDataLoader.ts @@ -152,7 +152,8 @@ export const useInfiniteDataLoader = < const previousDataRef = useRef(request.data) - const isLoading = requestRefs.current.some( + // isFetching is true when there is an active request in progress + const isFetching = requestRefs.current.some( req => req.status === StatusEnum.LOADING || optimisticIsLoadingRef.current, ) @@ -236,19 +237,28 @@ export const useInfiniteDataLoader = < getNextPage ? getNextPage(...params) : undefined }, [getNextPage]) + const computedData = + isLoadingFirstPage || + [...requestRefs.current].filter(dataloader => !!dataloader.data).length === + 0 + ? initialData + : ([...requestRefs.current] + .filter(dataloader => !!dataloader.data) + .map(dataloader => dataloader.data) as ResultType[]) + + // isLoading is true only when there is no cache data and we're fetching data for the first time + const isLoading = + !computedData && + request.isFirstLoading && + request.status === StatusEnum.LOADING + const data = useMemo>( () => ({ - data: - isLoadingFirstPage || - [...requestRefs.current].filter(dataloader => !!dataloader.data) - .length === 0 - ? initialData - : ([...requestRefs.current] - .filter(dataloader => !!dataloader.data) - .map(dataloader => dataloader.data) as ResultType[]), + data: computedData, error: request.error, hasNextPage: nextPageRef.current !== undefined, isError, + isFetching, isIdle, isLoading, isLoadingFirstPage, @@ -257,9 +267,10 @@ export const useInfiniteDataLoader = < reload, }), [ - initialData, + computedData, isIdle, isLoading, + isFetching, isSuccess, isError, request.error,