diff --git a/src/useMutate.test.tsx b/src/useMutate.test.tsx
index ce399a1..4220c04 100644
--- a/src/useMutate.test.tsx
+++ b/src/useMutate.test.tsx
@@ -34,6 +34,24 @@ describe("useMutate", () => {
});
});
+ it("should cancel on unmount", async () => {
+ nock("https://my-awesome-api.fake")
+ .delete("/plop")
+ .reply(200, { oops: true });
+ const wrapper: React.FC = ({ children }) => (
+ {children}
+ );
+
+ const resolve = jest.fn(() => "/plop");
+ const { result, unmount } = renderHook(() => useMutate("DELETE", resolve), { wrapper });
+
+ const resultPromise = result.current.mutate({});
+ unmount();
+ const res = await resultPromise;
+
+ expect(res).toEqual(undefined);
+ });
+
it("should call the correct url with a specific id", async () => {
nock("https://my-awesome-api.fake")
.delete("/plop")
@@ -419,7 +437,7 @@ describe("useMutate", () => {
});
describe("Mutate identity", () => {
- it("should remain the same across calls", async () => {
+ it("should remain the same across calls with static props", async () => {
nock("https://my-awesome-api.fake")
.post("/plop/one")
.reply(200, { id: 1 })
@@ -447,6 +465,34 @@ describe("useMutate", () => {
expect(mutate0).toEqual(mutate1);
expect(mutate0).toEqual(mutate2);
});
+
+ it("should remain the same across calls with deeply equal props", async () => {
+ nock("https://my-awesome-api.fake")
+ .post("/plop/one")
+ .reply(200, { id: 1 })
+ .persist();
+
+ const wrapper: React.FC = ({ children }) => (
+ {children}
+ );
+ const getPath = ({ id }: { id: string }) => `plop/${id}`;
+ const { result } = renderHook(
+ () =>
+ useMutate<{ id: number }, unknown, {}, {}, { id: string }>("POST", getPath, {
+ pathParams: { id: "one" },
+ }),
+ {
+ wrapper,
+ },
+ );
+ const mutate0 = result.current.mutate;
+ const mutate1 = result.current.mutate;
+ await result.current.mutate({});
+ const mutate2 = result.current.mutate;
+
+ expect(mutate0).toEqual(mutate1);
+ expect(mutate0).toEqual(mutate2);
+ });
});
describe("Path Params", () => {
diff --git a/src/useMutate.tsx b/src/useMutate.tsx
index 11acff2..6bd6371 100644
--- a/src/useMutate.tsx
+++ b/src/useMutate.tsx
@@ -1,11 +1,12 @@
import merge from "lodash/merge";
-import { useCallback, useContext, useEffect, useState } from "react";
+import { useContext, useEffect, useState } from "react";
import { Context } from "./Context";
import { MutateMethod, MutateState, MutateRequestOptions } from "./Mutate";
import { Omit, UseGetProps } from "./useGet";
import { constructUrl } from "./util/constructUrl";
import { processResponse } from "./util/processResponse";
import { useAbort } from "./useAbort";
+import { useDeepCompareCallback, useDeepCompareEffect } from "./util/useDeepCompareEffect";
export interface UseMutateProps
extends Omit, "lazy" | "debounce" | "mock"> {
@@ -94,13 +95,32 @@ export function useMutate<
useEffect(() => () => abort(), [abort]);
const { pathInlineBodyEncode, queryParamStringifyOptions, requestOptions, localErrorOnly, onMutate } = props;
- const mutate = useCallback>(
+
+ const effectDependencies = [
+ path,
+ pathParams,
+ queryParams,
+ verb,
+ isDelete,
+ base,
+ context,
+ queryParamStringifyOptions,
+ requestOptions,
+ onMutate,
+ abort,
+ pathInlineBodyEncode,
+ localErrorOnly,
+ resolve,
+ ];
+ const mutate = useDeepCompareCallback>(
async (body: TRequestBody, mutateRequestOptions?: MutateRequestOptions) => {
+ const signal = getAbortSignal();
+
setState(prevState => {
- if (prevState.loading) {
- abort();
+ if (prevState.error || !prevState.loading) {
+ return { ...prevState, loading: true, error: null };
}
- return { ...prevState, loading: true, error: null };
+ return prevState;
});
const pathStr =
@@ -129,8 +149,6 @@ export function useMutate<
options.body = (body as unknown) as string;
}
- const signal = getAbortSignal();
-
const url = constructUrl(
base,
pathParts.join("/"),
@@ -233,25 +251,13 @@ export function useMutate<
return data;
},
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [
- /* getAbortSignal - changes too much! */
- path,
- pathParams,
- queryParams,
- verb,
- isDelete,
- base,
- context,
- queryParamStringifyOptions,
- requestOptions,
- onMutate,
- abort,
- pathInlineBodyEncode,
- localErrorOnly,
- resolve,
- ],
+ effectDependencies,
);
+ useDeepCompareEffect(() => {
+ if (state.loading) {
+ abort();
+ }
+ }, effectDependencies);
return {
...state,