diff --git a/packages/use-dataloader/src/DataLoaderProvider.tsx b/packages/use-dataloader/src/DataLoaderProvider.tsx index 5dd988ff0..756030cb7 100644 --- a/packages/use-dataloader/src/DataLoaderProvider.tsx +++ b/packages/use-dataloader/src/DataLoaderProvider.tsx @@ -15,7 +15,7 @@ import { StatusEnum, } from './constants' import DataLoader from './dataloader' -import { OnErrorFn, PromiseType } from './types' +import { NeedPollingType, OnErrorFn, PromiseType } from './types' type CachedData = Record type Reloads = Record Promise> @@ -30,6 +30,7 @@ type UseDataLoaderInitializerArgs = { * Max time before data from previous success is considered as outdated (in millisecond) */ maxDataLifetime?: number + needPolling?: NeedPollingType } type GetCachedDataFn = { diff --git a/packages/use-dataloader/src/__tests__/dataloader.test.ts b/packages/use-dataloader/src/__tests__/dataloader.test.ts index 368cdd27e..e8e90be10 100644 --- a/packages/use-dataloader/src/__tests__/dataloader.test.ts +++ b/packages/use-dataloader/src/__tests__/dataloader.test.ts @@ -193,23 +193,84 @@ describe('Dataloader class', () => { }) await instance.load() expect(method).toBeCalledTimes(1) - await new Promise(resolve => { setTimeout(resolve, PROMISE_TIMEOUT * 3) }) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 3) + }) expect(method).toBeCalledTimes(2) - await new Promise(resolve => { setTimeout(resolve, PROMISE_TIMEOUT * 3) }) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 3) + }) expect(method).toBeCalledTimes(3) await instance.load() await instance.load() - await new Promise(resolve => { setTimeout(resolve) }) + await new Promise(resolve => { + setTimeout(resolve) + }) expect(method).toBeCalledTimes(4) await instance.load() await instance.load() await instance.load(true) - await new Promise(resolve => { setTimeout(resolve) }) + await new Promise(resolve => { + setTimeout(resolve) + }) expect(method).toBeCalledTimes(6) instance.setPollingInterval(PROMISE_TIMEOUT * 4) await instance.destroy() }) + test('should create instance with polling and needPolling', async () => { + const method = jest.fn(fakeSuccessPromise) + const instance = new DataLoader({ + key: 'test', + method, + needPolling: () => true, + pollingInterval: PROMISE_TIMEOUT * 2, + }) + await instance.load() + expect(method).toBeCalledTimes(1) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 3) + }) + expect(method).toBeCalledTimes(2) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 3) + }) + expect(method).toBeCalledTimes(3) + await instance.load() + await instance.load() + await new Promise(resolve => { + setTimeout(resolve) + }) + expect(method).toBeCalledTimes(4) + await instance.load() + await instance.load() + await instance.load(true) + await new Promise(resolve => { + setTimeout(resolve) + }) + expect(method).toBeCalledTimes(6) + instance.setPollingInterval(PROMISE_TIMEOUT * 4) + await instance.destroy() + }) + + test('should create instance with polling and needPolling that return false', async () => { + const method = jest.fn(fakeSuccessPromise) + const instance = new DataLoader({ + key: 'test', + method, + needPolling: () => false, + pollingInterval: PROMISE_TIMEOUT * 2, + }) + await instance.load() + expect(method).toBeCalledTimes(1) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 3) + }) + expect(method).toBeCalledTimes(1) + instance.setNeedPolling(true) + await instance.destroy() + }) + test('should update outdated data', async () => { const method = jest.fn(fakeSuccessPromise) const onSuccess = jest.fn() @@ -222,12 +283,16 @@ describe('Dataloader class', () => { instance.addOnSuccessListener(onSuccess) expect(instance.status).toBe(StatusEnum.LOADING) expect(method).toBeCalledTimes(1) - await new Promise(resolve => { setTimeout(resolve, PROMISE_TIMEOUT) }) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT) + }) expect(onSuccess).toBeCalledTimes(1) await instance.load() expect(method).toBeCalledTimes(1) expect(onSuccess).toBeCalledTimes(1) - await new Promise(resolve => { setTimeout(resolve, PROMISE_TIMEOUT * 2) }) + await new Promise(resolve => { + setTimeout(resolve, PROMISE_TIMEOUT * 2) + }) await instance.load() expect(method).toBeCalledTimes(2) expect(onSuccess).toBeCalledTimes(2) @@ -245,7 +310,9 @@ describe('Dataloader class', () => { expect(instance.status).toBe(StatusEnum.LOADING) } // Because wait for setTimeout tryLaunch in dataloader.ts - await new Promise(resolve => { setTimeout(resolve) }) + await new Promise(resolve => { + setTimeout(resolve) + }) expect(method).toBeCalledTimes(2) }) }) diff --git a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx index c3ba0f40b..20f4eb36a 100644 --- a/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx +++ b/packages/use-dataloader/src/__tests__/useDataLoader.test.tsx @@ -150,7 +150,7 @@ describe('useDataLoader', () => { method: () => new Promise(resolve => { setTimeout(() => resolve(null), PROMISE_TIMEOUT) - }) + }), }, wrapper, }) @@ -272,6 +272,7 @@ describe('useDataLoader', () => { test('should render correctly with pooling', async () => { const pollingProps = { config: { + needPolling: () => true, pollingInterval: PROMISE_TIMEOUT, }, key: 'test-6', diff --git a/packages/use-dataloader/src/dataloader.ts b/packages/use-dataloader/src/dataloader.ts index 01ddcbbe8..bb96d24d4 100644 --- a/packages/use-dataloader/src/dataloader.ts +++ b/packages/use-dataloader/src/dataloader.ts @@ -1,11 +1,18 @@ import { DEFAULT_MAX_CONCURRENT_REQUESTS, StatusEnum } from './constants' -import { OnCancelFn, OnErrorFn, OnSuccessFn, PromiseType } from './types' +import { + NeedPollingType, + OnCancelFn, + OnErrorFn, + OnSuccessFn, + PromiseType, +} from './types' export type DataLoaderConstructorArgs = { enabled?: boolean key: string method: () => PromiseType pollingInterval?: number + needPolling?: NeedPollingType maxDataLifetime?: number keepPreviousData?: boolean } @@ -25,6 +32,8 @@ class DataLoader { public pollingInterval?: number + public needPolling: NeedPollingType = true + public maxDataLifetime?: number public isDataOutdated = false @@ -61,6 +70,7 @@ class DataLoader { this.pollingInterval = args?.pollingInterval this.keepPreviousData = args?.keepPreviousData this.maxDataLifetime = args.maxDataLifetime + this.needPolling = args.needPolling ?? true if (args.enabled) { this.tryLaunch() } else { @@ -146,7 +156,13 @@ class DataLoader { } } DataLoader.started -= 1 - if (this.pollingInterval && !this.destroyed) { + if ( + this.pollingInterval && + !this.destroyed && + (typeof this.needPolling === 'function' + ? this.needPolling(DataLoader.cachedData[this.key] as T) + : this.needPolling) + ) { this.timeout = setTimeout( // eslint-disable-next-line @typescript-eslint/no-misused-promises this.launch, @@ -217,6 +233,7 @@ class DataLoader { } public async destroy(): Promise { + DataLoader.cachedData[this.key] = undefined await this.cancel?.() if (this.timeout) { clearTimeout(this.timeout) @@ -234,6 +251,10 @@ class DataLoader { this.tryLaunch() } } + + public setNeedPolling(needPolling: NeedPollingType): void { + this.needPolling = needPolling + } } export default DataLoader diff --git a/packages/use-dataloader/src/types.ts b/packages/use-dataloader/src/types.ts index c86b24355..3de0172cd 100644 --- a/packages/use-dataloader/src/types.ts +++ b/packages/use-dataloader/src/types.ts @@ -7,6 +7,7 @@ export type OnSuccessFn = | ((result: T) => void | Promise) | undefined export type OnCancelFn = (() => void | Promise) | undefined +export type NeedPollingType = boolean | ((data: T) => boolean) /** * @typedef {Object} UseDataLoaderConfig @@ -16,6 +17,7 @@ export type OnCancelFn = (() => void | Promise) | undefined * @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 {NeedPollingType} [needPolling=true] When pollingInterval is set you can set a set a custom callback to know if polling is enabled */ export interface UseDataLoaderConfig { enabled?: boolean @@ -28,6 +30,7 @@ export interface UseDataLoaderConfig { * Max time before data from previous success is considered as outdated (in millisecond) */ maxDataLifetime?: number + needPolling?: NeedPollingType } /** diff --git a/packages/use-dataloader/src/useDataLoader.ts b/packages/use-dataloader/src/useDataLoader.ts index 7019330f5..94fd743b8 100644 --- a/packages/use-dataloader/src/useDataLoader.ts +++ b/packages/use-dataloader/src/useDataLoader.ts @@ -28,6 +28,7 @@ const useDataLoader = ( onSuccess, pollingInterval, maxDataLifetime, + needPolling, }: UseDataLoaderConfig = {}, ): UseDataLoaderResult => { const isMountedRef = useRef(false) @@ -50,6 +51,7 @@ const useDataLoader = ( keepPreviousData, maxDataLifetime, method, + needPolling, pollingInterval, }) as DataLoader @@ -63,6 +65,7 @@ const useDataLoader = ( getOrAddRequest, maxDataLifetime, method, + needPolling, pollingInterval, keepPreviousData, subscribeFn, @@ -118,6 +121,12 @@ const useDataLoader = ( } }, [pollingInterval, request]) + useEffect(() => { + if (needPolling !== request.needPolling) { + request.setNeedPolling(needPolling ?? true) + } + }, [needPolling, request]) + useEffect(() => { isFetchingRef.current = isLoading || isPolling }, [isLoading, isPolling])