Skip to content

Commit 93d00e6

Browse files
Merge pull request #35 from myty/feature/use-async-effect
useAsyncEffect hook
2 parents a62463a + 2c14214 commit 93d00e6

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from "react";
2+
import { cleanup, render, waitFor } from "@testing-library/react";
3+
import { useAsyncEffect } from "./use-async-effect";
4+
import { CoreUtils } from "andculturecode-javascript-core";
5+
import { AsyncEffectCallback } from "../types/async-effect-callback-type";
6+
7+
describe("useAsyncEffect", () => {
8+
const setupUseAsyncEffect = (asyncEffect: AsyncEffectCallback) => {
9+
const TestComponent = () => {
10+
useAsyncEffect(asyncEffect, []);
11+
return null;
12+
};
13+
14+
render(<TestComponent />);
15+
};
16+
17+
test("executes async method", async () => {
18+
// Arrange
19+
const mockedMethod = jest.fn();
20+
21+
// Act
22+
setupUseAsyncEffect(async () => {
23+
await CoreUtils.sleep(1);
24+
mockedMethod();
25+
});
26+
27+
// Assert
28+
await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
29+
await cleanup();
30+
});
31+
32+
test("executes cleanup method", async () => {
33+
// Arrange
34+
const mockedMethod = jest.fn();
35+
const mockedCleanupMethod = jest.fn();
36+
37+
// Act
38+
setupUseAsyncEffect(async () => {
39+
await CoreUtils.sleep(1);
40+
mockedMethod();
41+
return mockedCleanupMethod;
42+
});
43+
44+
// Assert
45+
await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
46+
await cleanup();
47+
await waitFor(() => expect(mockedCleanupMethod).toBeCalledTimes(1));
48+
});
49+
50+
test("isMounted initially equals true", async () => {
51+
// Arrange
52+
let actualIsMountedValue: boolean = false;
53+
const expectedIsMountedValue: boolean = true;
54+
55+
// Act
56+
setupUseAsyncEffect(async (isMounted) => {
57+
actualIsMountedValue = isMounted();
58+
await CoreUtils.sleep(1);
59+
});
60+
61+
// Assert
62+
expect(actualIsMountedValue).toBe(expectedIsMountedValue);
63+
await cleanup();
64+
});
65+
66+
test("isMounted equals false after cleanup", async () => {
67+
// Arrange
68+
let actualIsMountedValue: boolean;
69+
const expectedIsMountedValue: boolean = false;
70+
const mockedMethod = jest.fn();
71+
72+
// Act
73+
setupUseAsyncEffect(async (isMounted) => {
74+
await CoreUtils.sleep(1);
75+
actualIsMountedValue = isMounted();
76+
mockedMethod();
77+
});
78+
79+
// Assert
80+
await cleanup();
81+
await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
82+
expect(actualIsMountedValue).toBe(expectedIsMountedValue);
83+
});
84+
});

src/hooks/use-async-effect.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, DependencyList, EffectCallback, useCallback } from "react";
2+
import { AsyncEffectCallback } from "../types/async-effect-callback-type";
3+
4+
/**
5+
* Version of the useEffect hook that accepts an async function
6+
* @export
7+
* @param {AsyncEffectCallback} asyncEffect
8+
* @param {DependencyList} deps
9+
*/
10+
export function useAsyncEffect(
11+
asyncEffect: AsyncEffectCallback,
12+
deps: DependencyList
13+
) {
14+
const asyncCallback = useCallback<AsyncEffectCallback>(asyncEffect, deps);
15+
16+
useEffect(() => {
17+
let cleanupMethod = () => {};
18+
let isMounted = true;
19+
20+
async function runAsyncCallback() {
21+
const result: ReturnType<EffectCallback> = await asyncCallback(
22+
() => isMounted
23+
);
24+
25+
if (typeof result === "function") {
26+
cleanupMethod = result;
27+
}
28+
}
29+
30+
runAsyncCallback();
31+
32+
return () => {
33+
cleanupMethod();
34+
isMounted = false;
35+
};
36+
}, [asyncCallback]);
37+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export { Redirects, RedirectsProps } from "./components/routing/redirects";
2727
// -----------------------------------------------------------------------------------------
2828

2929
export { makeCancellable } from "./hooks/make-cancellable";
30+
export { useAsyncEffect } from "./hooks/use-async-effect";
3031
export { useCancellablePromise } from "./hooks/use-cancellable-promise";
3132
export { useDebounce } from "./hooks/use-debounce";
3233
export { useLocalization } from "./hooks/use-localization";
@@ -61,6 +62,7 @@ export { ServiceHookFactory } from "./services/service-hook-factory";
6162
// #region Types
6263
// -----------------------------------------------------------------------------------------
6364

65+
export { AsyncEffectCallback } from "./types/async-effect-callback-type";
6466
export { BulkUpdateService } from "./types/bulk-update-service-type";
6567
export { BulkUpdateServiceHook } from "./types/bulk-update-service-hook-type";
6668
export { CreateService } from "./types/create-service-type";
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EffectCallback } from "react";
2+
3+
/**
4+
* Type defining the asyncEffect parameter from calling `useAsyncEffect()`
5+
*/
6+
export type AsyncEffectCallback = (
7+
isMounted: () => boolean
8+
) => Promise<ReturnType<EffectCallback>>;

0 commit comments

Comments
 (0)