diff --git a/src/hooks/use-async-effect.test.tsx b/src/hooks/use-async-effect.test.tsx
new file mode 100644
index 0000000..b141344
--- /dev/null
+++ b/src/hooks/use-async-effect.test.tsx
@@ -0,0 +1,84 @@
+import React from "react";
+import { cleanup, render, waitFor } from "@testing-library/react";
+import { useAsyncEffect } from "./use-async-effect";
+import { CoreUtils } from "andculturecode-javascript-core";
+import { AsyncEffectCallback } from "../types/async-effect-callback-type";
+
+describe("useAsyncEffect", () => {
+ const setupUseAsyncEffect = (asyncEffect: AsyncEffectCallback) => {
+ const TestComponent = () => {
+ useAsyncEffect(asyncEffect, []);
+ return null;
+ };
+
+ render();
+ };
+
+ test("executes async method", async () => {
+ // Arrange
+ const mockedMethod = jest.fn();
+
+ // Act
+ setupUseAsyncEffect(async () => {
+ await CoreUtils.sleep(1);
+ mockedMethod();
+ });
+
+ // Assert
+ await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
+ await cleanup();
+ });
+
+ test("executes cleanup method", async () => {
+ // Arrange
+ const mockedMethod = jest.fn();
+ const mockedCleanupMethod = jest.fn();
+
+ // Act
+ setupUseAsyncEffect(async () => {
+ await CoreUtils.sleep(1);
+ mockedMethod();
+ return mockedCleanupMethod;
+ });
+
+ // Assert
+ await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
+ await cleanup();
+ await waitFor(() => expect(mockedCleanupMethod).toBeCalledTimes(1));
+ });
+
+ test("isMounted initially equals true", async () => {
+ // Arrange
+ let actualIsMountedValue: boolean = false;
+ const expectedIsMountedValue: boolean = true;
+
+ // Act
+ setupUseAsyncEffect(async (isMounted) => {
+ actualIsMountedValue = isMounted();
+ await CoreUtils.sleep(1);
+ });
+
+ // Assert
+ expect(actualIsMountedValue).toBe(expectedIsMountedValue);
+ await cleanup();
+ });
+
+ test("isMounted equals false after cleanup", async () => {
+ // Arrange
+ let actualIsMountedValue: boolean;
+ const expectedIsMountedValue: boolean = false;
+ const mockedMethod = jest.fn();
+
+ // Act
+ setupUseAsyncEffect(async (isMounted) => {
+ await CoreUtils.sleep(1);
+ actualIsMountedValue = isMounted();
+ mockedMethod();
+ });
+
+ // Assert
+ await cleanup();
+ await waitFor(() => expect(mockedMethod).toBeCalledTimes(1));
+ expect(actualIsMountedValue).toBe(expectedIsMountedValue);
+ });
+});
diff --git a/src/hooks/use-async-effect.ts b/src/hooks/use-async-effect.ts
new file mode 100644
index 0000000..6523565
--- /dev/null
+++ b/src/hooks/use-async-effect.ts
@@ -0,0 +1,37 @@
+import { useEffect, DependencyList, EffectCallback, useCallback } from "react";
+import { AsyncEffectCallback } from "../types/async-effect-callback-type";
+
+/**
+ * Version of the useEffect hook that accepts an async function
+ * @export
+ * @param {AsyncEffectCallback} asyncEffect
+ * @param {DependencyList} deps
+ */
+export function useAsyncEffect(
+ asyncEffect: AsyncEffectCallback,
+ deps: DependencyList
+) {
+ const asyncCallback = useCallback(asyncEffect, deps);
+
+ useEffect(() => {
+ let cleanupMethod = () => {};
+ let isMounted = true;
+
+ async function runAsyncCallback() {
+ const result: ReturnType = await asyncCallback(
+ () => isMounted
+ );
+
+ if (typeof result === "function") {
+ cleanupMethod = result;
+ }
+ }
+
+ runAsyncCallback();
+
+ return () => {
+ cleanupMethod();
+ isMounted = false;
+ };
+ }, [asyncCallback]);
+}
diff --git a/src/index.ts b/src/index.ts
index 268c229..491b8d3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -27,6 +27,7 @@ export { Redirects, RedirectsProps } from "./components/routing/redirects";
// -----------------------------------------------------------------------------------------
export { makeCancellable } from "./hooks/make-cancellable";
+export { useAsyncEffect } from "./hooks/use-async-effect";
export { useCancellablePromise } from "./hooks/use-cancellable-promise";
export { useDebounce } from "./hooks/use-debounce";
export { useLocalization } from "./hooks/use-localization";
@@ -61,6 +62,7 @@ export { ServiceHookFactory } from "./services/service-hook-factory";
// #region Types
// -----------------------------------------------------------------------------------------
+export { AsyncEffectCallback } from "./types/async-effect-callback-type";
export { BulkUpdateService } from "./types/bulk-update-service-type";
export { BulkUpdateServiceHook } from "./types/bulk-update-service-hook-type";
export { CreateService } from "./types/create-service-type";
diff --git a/src/types/async-effect-callback-type.ts b/src/types/async-effect-callback-type.ts
new file mode 100644
index 0000000..314afe8
--- /dev/null
+++ b/src/types/async-effect-callback-type.ts
@@ -0,0 +1,8 @@
+import { EffectCallback } from "react";
+
+/**
+ * Type defining the asyncEffect parameter from calling `useAsyncEffect()`
+ */
+export type AsyncEffectCallback = (
+ isMounted: () => boolean
+) => Promise>;