Skip to content

Commit 7b39c50

Browse files
feat: introduce datalifetime in dataloader (#817)
1 parent 31e680b commit 7b39c50

File tree

6 files changed

+117
-26
lines changed

6 files changed

+117
-26
lines changed

packages/use-dataloader/README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,21 +228,22 @@ const useDataLoader = (
228228
onError, // Callback when a error is occured
229229
initialData, // Initial data if no one is present in the cache before the request
230230
pollingInterval, // Relaunch the request after the last success
231+
needPolling = true, // If true or function return true it will execute the polling
231232
enabled = true, // Launch request automatically
232233
keepPreviousData = true, // Do we need to keep the previous data after reload
233-
maxDataLifetime, // Max time before previous success data is outdated (in millisecond)
234+
dataLifetime, // Max time before previous success data is outdated (in millisecond). By default refetch on every mount
234235
} = {},
235236
)
236237
```
237238

238-
| Property | Description |
239-
| :----------: | :-------------------------------------------------------------------------------------------------------------------: |
240-
| isIdle | `true` if the request is not launched |
241-
| isLoading | `true` if the request is launched **or** enabled is `true` and isIdle is `true` |
242-
| isSuccess | `true`if the request finished successfully |
243-
| isError | `true` if the request throw an error |
244-
| isPolling | `true` if the request if `enabled` is true, `pollingInterval` is defined and the status is `isLoading` or `isSuccess` |
245-
| previousData | if `keepPreviousData` is true it return the last data fetched |
246-
| data | return the `initialData` if no data is fetched or not present in the cache otherwise return the data fetched |
247-
| error | return the error occured during the request |
248-
| reload | allow you to reload the data (it doesn't clear the actual data) |
239+
| Property | Description |
240+
| :----------: | :------------------------------------------------------------------------------------------------------------------------------------------: |
241+
| isIdle | `true` if the request is not launched |
242+
| isLoading | `true` if the request is launched **or** enabled is `true` and isIdle is `true` |
243+
| isSuccess | `true`if the request finished successfully |
244+
| isError | `true` if the request throw an error |
245+
| isPolling | `true` if the request if `enabled` is true, `pollingInterval` is defined and the status is `isLoading`,`isSuccess` or during the first fetch |
246+
| previousData | if `keepPreviousData` is true it return the last data fetched |
247+
| data | return the `initialData` if no data is fetched or not present in the cache otherwise return the data fetched |
248+
| error | return the error occured during the request |
249+
| reload | allow you to reload the data (it doesn't clear the actual data) |

packages/use-dataloader/src/__tests__/useDataLoader.test.tsx

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ type UseDataLoaderHookProps = {
1515

1616
const PROMISE_TIMEOUT = 50
1717

18+
const fakeSuccessPromise = () =>
19+
new Promise(resolve => {
20+
setTimeout(() => resolve(true), PROMISE_TIMEOUT)
21+
})
22+
1823
const initialProps = {
1924
config: {
2025
enabled: true,
2126
keepPreviousData: true,
2227
},
2328
key: 'test',
24-
method: jest.fn(
25-
() =>
26-
new Promise(resolve => {
27-
setTimeout(() => resolve(true), PROMISE_TIMEOUT)
28-
}),
29-
),
29+
method: jest.fn(fakeSuccessPromise),
3030
}
3131
const wrapper = ({ children }: { children?: ReactNode }) => (
3232
<DataLoaderProvider>{children}</DataLoaderProvider>
@@ -735,5 +735,85 @@ describe('useDataLoader', () => {
735735
await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
736736
expect(mockedFn).toBeCalledTimes(2)
737737
})
738+
739+
test('should render correctly with dataLifetime prevent double call', async () => {
740+
const testingProps = {
741+
config: {
742+
dataLifetime: 1000,
743+
enabled: true,
744+
},
745+
config2: {
746+
dataLifetime: 1000,
747+
enabled: false,
748+
},
749+
key: 'test-datalifetime',
750+
method: jest.fn(fakeSuccessPromise),
751+
}
752+
const { result, rerender } = renderHook(
753+
props => [
754+
useDataLoader(props.key, props.method, props.config),
755+
useDataLoader(props.key, props.method, props.config2),
756+
],
757+
{
758+
initialProps: testingProps,
759+
wrapper,
760+
},
761+
)
762+
expect(result.current[0].data).toBe(undefined)
763+
expect(result.current[0].isLoading).toBe(true)
764+
expect(result.current[0].previousData).toBe(undefined)
765+
expect(testingProps.method).toBeCalledTimes(1)
766+
await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
767+
testingProps.config2.enabled = true
768+
rerender(testingProps)
769+
expect(testingProps.method).toBeCalledTimes(1)
770+
expect(result.current[0].data).toBe(true)
771+
expect(result.current[1].data).toBe(true)
772+
expect(result.current[0].isLoading).toBe(false)
773+
expect(result.current[1].isLoading).toBe(false)
774+
expect(result.current[0].previousData).toBe(undefined)
775+
expect(result.current[1].previousData).toBe(undefined)
776+
})
777+
778+
test('should render correctly with dataLifetime dont prevent double call', async () => {
779+
const testingProps = {
780+
config: {
781+
enabled: true,
782+
},
783+
config2: {
784+
enabled: false,
785+
},
786+
key: 'test-no-datalifetime',
787+
method: jest.fn(fakeSuccessPromise),
788+
}
789+
const { result, rerender } = renderHook(
790+
props => [
791+
useDataLoader(props.key, props.method, props.config),
792+
useDataLoader(props.key, props.method, props.config2),
793+
],
794+
{
795+
initialProps: testingProps,
796+
wrapper,
797+
},
798+
)
799+
expect(result.current[0].data).toBe(undefined)
800+
expect(result.current[0].isLoading).toBe(true)
801+
expect(result.current[0].previousData).toBe(undefined)
802+
expect(testingProps.method).toBeCalledTimes(1)
803+
await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
804+
testingProps.config2.enabled = true
805+
rerender(testingProps)
806+
await waitFor(() => expect(result.current[0].isLoading).toBe(true))
807+
await waitFor(() => expect(result.current[1].isLoading).toBe(true))
808+
expect(testingProps.method).toBeCalledTimes(2)
809+
expect(result.current[0].data).toBe(true)
810+
expect(result.current[0].isLoading).toBe(true)
811+
expect(result.current[0].previousData).toBe(undefined)
812+
expect(result.current[1].data).toBe(true)
813+
expect(result.current[1].isLoading).toBe(true)
814+
expect(result.current[1].previousData).toBe(undefined)
815+
await waitFor(() => expect(result.current[0].isSuccess).toBe(true))
816+
await waitFor(() => expect(result.current[1].isSuccess).toBe(true))
817+
})
738818
})
739819
/* eslint-enable no-console */

packages/use-dataloader/src/dataloader.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class DataLoader<ResultType = unknown, ErrorType = unknown> {
3939

4040
public isFirstLoading = true
4141

42+
public dataUpdatedAt?: number
43+
4244
public constructor(args: DataLoaderConstructorArgs<ResultType>) {
4345
this.key = args.key
4446
this.method = args.method
@@ -102,6 +104,7 @@ class DataLoader<ResultType = unknown, ErrorType = unknown> {
102104
this.status = StatusEnum.SUCCESS
103105
this.data = data
104106
this.error = undefined
107+
this.dataUpdatedAt = Date.now()
105108
}
106109
this.isCalled = false
107110
this.isFirstLoading = false

packages/use-dataloader/src/types.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type NeedPollingType<T = unknown> = boolean | ((data?: T) => boolean)
1717
* @property {number} [pollingInterval] relaunch the request after the last success
1818
* @property {boolean} [enabled=true] launch request automatically (default true)
1919
* @property {boolean} [keepPreviousData=true] do we need to keep the previous data after reload (default true)
20+
* @property {number} [dataLifetime=undefined] Time before data from previous success is considered as outdated (in millisecond)
2021
* @property {NeedPollingType} [needPolling=true] When pollingInterval is set you can set a set a custom callback to know if polling is enabled
2122
*/
2223
export interface UseDataLoaderConfig<T = unknown> {
@@ -26,10 +27,7 @@ export interface UseDataLoaderConfig<T = unknown> {
2627
onError?: OnErrorFn
2728
onSuccess?: OnSuccessFn
2829
pollingInterval?: number
29-
/**
30-
* Max time before data from previous success is considered as outdated (in millisecond)
31-
*/
32-
maxDataLifetime?: number
30+
dataLifetime?: number
3331
needPolling?: NeedPollingType<T>
3432
}
3533

packages/use-dataloader/src/useDataLoader.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function useDataLoader<ResultType, ErrorType = Error>(
1515
needPolling = true,
1616
pollingInterval,
1717
initialData,
18+
dataLifetime,
1819
}: UseDataLoaderConfig<ResultType> = {},
1920
): UseDataLoaderResult<ResultType, ErrorType> {
2021
const { getOrAddRequest, onError: onGlobalError } = useDataLoaderContext()
@@ -86,10 +87,18 @@ function useDataLoader<ResultType, ErrorType = Error>(
8687
}, [request.data, keepPreviousData])
8788

8889
useEffect(() => {
89-
if (enabled && !request.isCalled) {
90+
// If this request is enabled and not already called
91+
if (
92+
enabled &&
93+
(!request.dataUpdatedAt ||
94+
!dataLifetime ||
95+
(request.dataUpdatedAt &&
96+
dataLifetime &&
97+
request.dataUpdatedAt + dataLifetime < Date.now()))
98+
) {
9099
request.load().then(onSuccessRef.current).catch(onErrorRef.current)
91100
}
92-
}, [enabled, request, keepPreviousData])
101+
}, [enabled, request, keepPreviousData, dataLifetime])
93102

94103
useEffect(() => {
95104
let interval: NodeJS.Timer

packages/use-dataloader/src/usePaginatedDataLoader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const usePaginatedDataLoader = <T>(
2424
onError,
2525
onSuccess,
2626
pollingInterval,
27-
maxDataLifetime,
27+
dataLifetime,
2828
needPolling,
2929
initialPage,
3030
perPage = 1,
@@ -51,10 +51,10 @@ const usePaginatedDataLoader = <T>(
5151
reload,
5252
error,
5353
} = useDataLoader(`${baseFetchKey}-page-${page}`, pageMethod, {
54+
dataLifetime,
5455
enabled,
5556
initialData,
5657
keepPreviousData,
57-
maxDataLifetime,
5858
needPolling,
5959
onError,
6060
onSuccess,

0 commit comments

Comments
 (0)