diff --git a/packages/use-dataloader/README.md b/packages/use-dataloader/README.md
index d97dc54d0..f0311e21f 100644
--- a/packages/use-dataloader/README.md
+++ b/packages/use-dataloader/README.md
@@ -228,21 +228,22 @@ const useDataLoader = (
onError, // Callback when a error is occured
initialData, // Initial data if no one is present in the cache before the request
pollingInterval, // Relaunch the request after the last success
+ needPolling = true, // If true or function return true it will execute the polling
enabled = true, // Launch request automatically
keepPreviousData = true, // Do we need to keep the previous data after reload
- maxDataLifetime, // Max time before previous success data is outdated (in millisecond)
+ dataLifetime, // Max time before previous success data is outdated (in millisecond). By default refetch on every mount
} = {},
)
```
-| 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` |
-| 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` or `isSuccess` |
-| previousData | if `keepPreviousData` is true it return the last data fetched |
-| data | return the `initialData` if no data is fetched or not present in the cache otherwise return the data fetched |
-| error | return the error occured during the request |
-| reload | allow you to reload the data (it doesn't clear the actual data) |
+| 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` |
+| 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 |
+| previousData | if `keepPreviousData` is true it return the last data fetched |
+| data | return the `initialData` if no data is fetched or not present in the cache otherwise return the data fetched |
+| error | return the error occured during the request |
+| reload | allow you to reload the data (it doesn't clear the actual data) |
diff --git a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx
index 927163d35..00f90d090 100644
--- a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx
+++ b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx
@@ -15,18 +15,18 @@ type UseDataLoaderHookProps = {
const PROMISE_TIMEOUT = 50
+const fakeSuccessPromise = () =>
+ new Promise(resolve => {
+ setTimeout(() => resolve(true), PROMISE_TIMEOUT)
+ })
+
const initialProps = {
config: {
enabled: true,
keepPreviousData: true,
},
key: 'test',
- method: jest.fn(
- () =>
- new Promise(resolve => {
- setTimeout(() => resolve(true), PROMISE_TIMEOUT)
- }),
- ),
+ method: jest.fn(fakeSuccessPromise),
}
const wrapper = ({ children }: { children?: ReactNode }) => (
{children}
@@ -735,5 +735,85 @@ describe('useDataLoader', () => {
await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
expect(mockedFn).toBeCalledTimes(2)
})
+
+ test('should render correctly with dataLifetime prevent double call', async () => {
+ const testingProps = {
+ config: {
+ dataLifetime: 1000,
+ enabled: true,
+ },
+ config2: {
+ dataLifetime: 1000,
+ enabled: false,
+ },
+ key: 'test-datalifetime',
+ method: jest.fn(fakeSuccessPromise),
+ }
+ const { result, rerender } = renderHook(
+ props => [
+ useDataLoader(props.key, props.method, props.config),
+ useDataLoader(props.key, props.method, props.config2),
+ ],
+ {
+ initialProps: testingProps,
+ wrapper,
+ },
+ )
+ expect(result.current[0].data).toBe(undefined)
+ expect(result.current[0].isLoading).toBe(true)
+ expect(result.current[0].previousData).toBe(undefined)
+ expect(testingProps.method).toBeCalledTimes(1)
+ await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
+ testingProps.config2.enabled = true
+ rerender(testingProps)
+ expect(testingProps.method).toBeCalledTimes(1)
+ expect(result.current[0].data).toBe(true)
+ expect(result.current[1].data).toBe(true)
+ expect(result.current[0].isLoading).toBe(false)
+ expect(result.current[1].isLoading).toBe(false)
+ expect(result.current[0].previousData).toBe(undefined)
+ expect(result.current[1].previousData).toBe(undefined)
+ })
+
+ test('should render correctly with dataLifetime dont prevent double call', async () => {
+ const testingProps = {
+ config: {
+ enabled: true,
+ },
+ config2: {
+ enabled: false,
+ },
+ key: 'test-no-datalifetime',
+ method: jest.fn(fakeSuccessPromise),
+ }
+ const { result, rerender } = renderHook(
+ props => [
+ useDataLoader(props.key, props.method, props.config),
+ useDataLoader(props.key, props.method, props.config2),
+ ],
+ {
+ initialProps: testingProps,
+ wrapper,
+ },
+ )
+ expect(result.current[0].data).toBe(undefined)
+ expect(result.current[0].isLoading).toBe(true)
+ expect(result.current[0].previousData).toBe(undefined)
+ expect(testingProps.method).toBeCalledTimes(1)
+ 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))
+ expect(testingProps.method).toBeCalledTimes(2)
+ expect(result.current[0].data).toBe(true)
+ expect(result.current[0].isLoading).toBe(true)
+ expect(result.current[0].previousData).toBe(undefined)
+ expect(result.current[1].data).toBe(true)
+ expect(result.current[1].isLoading).toBe(true)
+ expect(result.current[1].previousData).toBe(undefined)
+ await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
+ await waitFor(() => expect(result.current[1].isSuccess).toBe(true))
+ })
})
/* eslint-enable no-console */
diff --git a/packages/use-dataloader/src/dataloader.ts b/packages/use-dataloader/src/dataloader.ts
index 113971b8e..4dad0814e 100644
--- a/packages/use-dataloader/src/dataloader.ts
+++ b/packages/use-dataloader/src/dataloader.ts
@@ -39,6 +39,8 @@ class DataLoader {
public isFirstLoading = true
+ public dataUpdatedAt?: number
+
public constructor(args: DataLoaderConstructorArgs) {
this.key = args.key
this.method = args.method
@@ -102,6 +104,7 @@ class DataLoader {
this.status = StatusEnum.SUCCESS
this.data = data
this.error = undefined
+ this.dataUpdatedAt = Date.now()
}
this.isCalled = false
this.isFirstLoading = false
diff --git a/packages/use-dataloader/src/types.ts b/packages/use-dataloader/src/types.ts
index 1655ac84a..e03a3d984 100644
--- a/packages/use-dataloader/src/types.ts
+++ b/packages/use-dataloader/src/types.ts
@@ -17,6 +17,7 @@ export type NeedPollingType = boolean | ((data?: T) => boolean)
* @property {number} [pollingInterval] relaunch the request after the last success
* @property {boolean} [enabled=true] launch request automatically (default true)
* @property {boolean} [keepPreviousData=true] do we need to keep the previous data after reload (default true)
+ * @property {number} [dataLifetime=undefined] Time before data from previous success is considered as outdated (in millisecond)
* @property {NeedPollingType} [needPolling=true] When pollingInterval is set you can set a set a custom callback to know if polling is enabled
*/
export interface UseDataLoaderConfig {
@@ -26,10 +27,7 @@ export interface UseDataLoaderConfig {
onError?: OnErrorFn
onSuccess?: OnSuccessFn
pollingInterval?: number
- /**
- * Max time before data from previous success is considered as outdated (in millisecond)
- */
- maxDataLifetime?: number
+ dataLifetime?: number
needPolling?: NeedPollingType
}
diff --git a/packages/use-dataloader/src/useDataLoader.ts b/packages/use-dataloader/src/useDataLoader.ts
index 545cf810f..7c903d463 100644
--- a/packages/use-dataloader/src/useDataLoader.ts
+++ b/packages/use-dataloader/src/useDataLoader.ts
@@ -15,6 +15,7 @@ function useDataLoader(
needPolling = true,
pollingInterval,
initialData,
+ dataLifetime,
}: UseDataLoaderConfig = {},
): UseDataLoaderResult {
const { getOrAddRequest, onError: onGlobalError } = useDataLoaderContext()
@@ -86,10 +87,18 @@ function useDataLoader(
}, [request.data, keepPreviousData])
useEffect(() => {
- if (enabled && !request.isCalled) {
+ // If this request is enabled and not already called
+ if (
+ enabled &&
+ (!request.dataUpdatedAt ||
+ !dataLifetime ||
+ (request.dataUpdatedAt &&
+ dataLifetime &&
+ request.dataUpdatedAt + dataLifetime < Date.now()))
+ ) {
request.load().then(onSuccessRef.current).catch(onErrorRef.current)
}
- }, [enabled, request, keepPreviousData])
+ }, [enabled, request, keepPreviousData, dataLifetime])
useEffect(() => {
let interval: NodeJS.Timer
diff --git a/packages/use-dataloader/src/usePaginatedDataLoader.ts b/packages/use-dataloader/src/usePaginatedDataLoader.ts
index 4370bcb56..a2df1be76 100644
--- a/packages/use-dataloader/src/usePaginatedDataLoader.ts
+++ b/packages/use-dataloader/src/usePaginatedDataLoader.ts
@@ -24,7 +24,7 @@ const usePaginatedDataLoader = (
onError,
onSuccess,
pollingInterval,
- maxDataLifetime,
+ dataLifetime,
needPolling,
initialPage,
perPage = 1,
@@ -51,10 +51,10 @@ const usePaginatedDataLoader = (
reload,
error,
} = useDataLoader(`${baseFetchKey}-page-${page}`, pageMethod, {
+ dataLifetime,
enabled,
initialData,
keepPreviousData,
- maxDataLifetime,
needPolling,
onError,
onSuccess,