Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/use-dataloader/src/DataLoaderProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>
type Reloads = Record<string, () => Promise<void | unknown>>
Expand All @@ -30,6 +30,7 @@ type UseDataLoaderInitializerArgs<T = unknown> = {
* Max time before data from previous success is considered as outdated (in millisecond)
*/
maxDataLifetime?: number
needPolling?: NeedPollingType<T>
}

type GetCachedDataFn = {
Expand Down
81 changes: 74 additions & 7 deletions packages/use-dataloader/src/__tests__/dataloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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)
})
})
3 changes: 2 additions & 1 deletion packages/use-dataloader/src/__tests__/useDataLoader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe('useDataLoader', () => {
method: () =>
new Promise(resolve => {
setTimeout(() => resolve(null), PROMISE_TIMEOUT)
})
}),
},
wrapper,
})
Expand Down Expand Up @@ -272,6 +272,7 @@ describe('useDataLoader', () => {
test('should render correctly with pooling', async () => {
const pollingProps = {
config: {
needPolling: () => true,
pollingInterval: PROMISE_TIMEOUT,
},
key: 'test-6',
Expand Down
25 changes: 23 additions & 2 deletions packages/use-dataloader/src/dataloader.ts
Original file line number Diff line number Diff line change
@@ -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<T = unknown> = {
enabled?: boolean
key: string
method: () => PromiseType<T>
pollingInterval?: number
needPolling?: NeedPollingType<T>
maxDataLifetime?: number
keepPreviousData?: boolean
}
Expand All @@ -25,6 +32,8 @@ class DataLoader<T = unknown> {

public pollingInterval?: number

public needPolling: NeedPollingType<T> = true

public maxDataLifetime?: number

public isDataOutdated = false
Expand Down Expand Up @@ -61,6 +70,7 @@ class DataLoader<T = unknown> {
this.pollingInterval = args?.pollingInterval
this.keepPreviousData = args?.keepPreviousData
this.maxDataLifetime = args.maxDataLifetime
this.needPolling = args.needPolling ?? true
if (args.enabled) {
this.tryLaunch()
} else {
Expand Down Expand Up @@ -146,7 +156,13 @@ class DataLoader<T = unknown> {
}
}
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,
Expand Down Expand Up @@ -217,6 +233,7 @@ class DataLoader<T = unknown> {
}

public async destroy(): Promise<void> {
DataLoader.cachedData[this.key] = undefined
await this.cancel?.()
if (this.timeout) {
clearTimeout(this.timeout)
Expand All @@ -234,6 +251,10 @@ class DataLoader<T = unknown> {
this.tryLaunch()
}
}

public setNeedPolling(needPolling: NeedPollingType<T>): void {
this.needPolling = needPolling
}
}

export default DataLoader
3 changes: 3 additions & 0 deletions packages/use-dataloader/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type OnSuccessFn<T = unknown> =
| ((result: T) => void | Promise<void>)
| undefined
export type OnCancelFn = (() => void | Promise<void>) | undefined
export type NeedPollingType<T = unknown> = boolean | ((data: T) => boolean)

/**
* @typedef {Object} UseDataLoaderConfig
Expand All @@ -16,6 +17,7 @@ export type OnCancelFn = (() => void | Promise<void>) | 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<T = unknown> {
enabled?: boolean
Expand All @@ -28,6 +30,7 @@ export interface UseDataLoaderConfig<T = unknown> {
* Max time before data from previous success is considered as outdated (in millisecond)
*/
maxDataLifetime?: number
needPolling?: NeedPollingType
}

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/use-dataloader/src/useDataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const useDataLoader = <T>(
onSuccess,
pollingInterval,
maxDataLifetime,
needPolling,
}: UseDataLoaderConfig<T> = {},
): UseDataLoaderResult<T> => {
const isMountedRef = useRef(false)
Expand All @@ -50,6 +51,7 @@ const useDataLoader = <T>(
keepPreviousData,
maxDataLifetime,
method,
needPolling,
pollingInterval,
}) as DataLoader<T>

Expand All @@ -63,6 +65,7 @@ const useDataLoader = <T>(
getOrAddRequest,
maxDataLifetime,
method,
needPolling,
pollingInterval,
keepPreviousData,
subscribeFn,
Expand Down Expand Up @@ -118,6 +121,12 @@ const useDataLoader = <T>(
}
}, [pollingInterval, request])

useEffect(() => {
if (needPolling !== request.needPolling) {
request.setNeedPolling(needPolling ?? true)
}
}, [needPolling, request])

useEffect(() => {
isFetchingRef.current = isLoading || isPolling
}, [isLoading, isPolling])
Expand Down