Skip to content

Commit 6c1ec57

Browse files
committed
✨ add useAsyncTaskLazy and its tests
1 parent ff0e60a commit 6c1ec57

13 files changed

Lines changed: 148 additions & 50 deletions
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
export type AsyncTaskParams = {
2-
signal?: AbortSignal;
3-
};
1+
import type AsyncTaskParams from './AsyncTaskParams';
42

53
type AsyncTask<Result> = (params: AsyncTaskParams) => Promise<Result>;
64

src/AsyncTaskParams.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type AsyncTaskParams = {
2+
signal?: AbortSignal;
3+
};
4+
5+
export default AsyncTaskParams;

src/hooks/useImmediateAsyncTask.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
11
import { useLayoutEffect, useRef } from 'react';
2-
import type AsyncTask from './AsyncTask';
3-
import useLazyAsyncTask from './useLazyAsyncTask';
2+
import useLazyAsyncTask from '../useAsyncTaskLazy';
3+
import type AsyncTask from '../AsyncTask';
44

55
export type ImmediateAsyncTask<Result> = Readonly<{
66
error: Error | null;
77
result: Result | null;
88
pending: boolean;
9-
executeTask: () => Promise<void>;
109
}>;
1110

1211
function useImmediateAsyncTask<Result>(
1312
task: AsyncTask<Result>,
1413
): ImmediateAsyncTask<Result> {
1514
const firstRenderRef = useRef(true);
1615

17-
const { error, result, pending, executeTask } = useLazyAsyncTask(task);
16+
const { error, result, pending, executeAsyncTask } =
17+
useLazyAsyncTask<Result>();
1818

1919
useLayoutEffect(() => {
20-
executeTask();
20+
executeAsyncTask(task);
2121

2222
return () => {
2323
firstRenderRef.current = false;
2424
};
25-
}, [executeTask]);
25+
}, [task, executeAsyncTask]);
2626

2727
return {
2828
error,
2929
result,
3030
pending: firstRenderRef.current || pending,
31-
executeTask,
3231
};
3332
}
3433

src/hooks/useLazyAsyncTask.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
export { default as useLazyAsyncTask } from './hooks/useLazyAsyncTask';
1+
export { default as useAsyncTaskLazy } from './useAsyncTaskLazy';
22
export { default as useImmediateAsyncTask } from './hooks/useImmediateAsyncTask';
3-
export { default as useImperativeAsyncTask } from './hooks/useImperativeAsyncTask';
4-
export type { LazyAsyncTask } from './hooks/useLazyAsyncTask';
3+
export type { AsyncTaskLazyResult } from './useAsyncTaskLazy';
54
export type { ImmediateAsyncTask } from './hooks/useImmediateAsyncTask';
6-
export type { ImperativeAsyncTask } from './hooks/useImperativeAsyncTask';

src/useAsyncTaskLazy/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './useAsyncTaskLazy';
2+
export type { AsyncTaskLazyResult } from './useAsyncTaskLazy';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** @jest-environment jsdom */
2+
3+
import isAbortError from '../isAbortError';
4+
5+
describe('isAbortError | unit tests', () => {
6+
describe("when environment doesn't support DOMException", () => {
7+
const ORIGINAL_DOM_EXCEPTION = window.DOMException;
8+
9+
beforeEach(() => {
10+
// @ts-expect-error it simulates the absence of 'DOMException'.
11+
delete window.DOMException;
12+
});
13+
14+
afterEach(() => {
15+
window.DOMException = ORIGINAL_DOM_EXCEPTION;
16+
});
17+
18+
it("doesn't throw an error", () => {
19+
expect(isAbortError(new Error('Oops!'))).toBe(false);
20+
});
21+
});
22+
23+
it('checks if the error is an AbortError', () => {
24+
expect(isAbortError(new DOMException('Oops!', 'AbortError'))).toBe(true);
25+
expect(isAbortError(new DOMException('Oops!', 'OtherError'))).toBe(false);
26+
expect(isAbortError(new Error('AbortError'))).toBe(false);
27+
expect(isAbortError('AbortError')).toBe(false);
28+
});
29+
});
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/** @jest-environment jsdom */
2+
3+
import { act, renderHook } from '@testing-library/react';
4+
import useAsyncTaskLazy from './useAsyncTaskLazy';
5+
6+
describe('useAsyncTaskLazy | integration tests', () => {
7+
it('keep track of the pending state', async () => {
8+
// eslint-disable-next-line prettier/prettier
9+
const { result } = renderHook(useAsyncTaskLazy<void>);
10+
11+
expect(result.current.pending).toBe(false);
12+
13+
act(() => {
14+
result.current.executeAsyncTask(() => Promise.resolve());
15+
});
16+
17+
expect(result.current.pending).toBe(true);
18+
19+
await act(() => Promise.resolve());
20+
21+
expect(result.current.pending).toBe(false);
22+
});
23+
24+
describe('when task finishes', () => {
25+
it('returns the result', async () => {
26+
// eslint-disable-next-line prettier/prettier
27+
const { result } = renderHook(useAsyncTaskLazy<number>);
28+
29+
expect(result.current.result).toBe(null);
30+
31+
act(() => {
32+
result.current.executeAsyncTask(() => Promise.resolve(10));
33+
});
34+
35+
expect(result.current.result).toBe(null);
36+
37+
await act(() => Promise.resolve());
38+
39+
expect(result.current.result).toBe(10);
40+
});
41+
});
42+
43+
describe('when task fails', () => {
44+
it('returns the error', async () => {
45+
// eslint-disable-next-line prettier/prettier
46+
const { result } = renderHook(useAsyncTaskLazy<number>);
47+
48+
expect(result.current.result).toBe(null);
49+
50+
const error = new Error('Ooops!');
51+
52+
act(() => {
53+
result.current.executeAsyncTask(() => Promise.reject(error));
54+
});
55+
56+
expect(result.current.error).toBe(null);
57+
58+
await act(() => Promise.resolve());
59+
60+
expect(result.current.error).toBe(error);
61+
});
62+
});
63+
64+
describe('when component unmounts', () => {
65+
it('aborts the task', async () => {
66+
// eslint-disable-next-line prettier/prettier
67+
const { result, unmount } = renderHook(useAsyncTaskLazy<number>);
68+
69+
const handler = jest.fn();
70+
71+
act(() => {
72+
result.current.executeAsyncTask(({ signal }) => {
73+
return new Promise((resolve, reject) => {
74+
signal?.addEventListener('abort', () => {
75+
handler();
76+
reject(new DOMException('Aborted!', 'AbortError'));
77+
});
78+
79+
setTimeout(() => resolve(Math.random()), 100);
80+
});
81+
});
82+
});
83+
84+
expect(handler).not.toHaveBeenCalled();
85+
86+
unmount();
87+
88+
expect(handler).toHaveBeenCalled();
89+
});
90+
});
91+
});

0 commit comments

Comments
 (0)