diff --git a/src/useMutate.test.tsx b/src/useMutate.test.tsx
index d156665..ce399a1 100644
--- a/src/useMutate.test.tsx
+++ b/src/useMutate.test.tsx
@@ -418,6 +418,37 @@ describe("useMutate", () => {
});
});
+ describe("Mutate identity", () => {
+ it("should remain the same across calls", 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 pathParams = { id: "one" };
+ const { result } = renderHook(
+ () =>
+ useMutate<{ id: number }, unknown, {}, {}, { id: string }>("POST", getPath, {
+ pathParams,
+ }),
+ {
+ 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", () => {
it("should resolve path parameters if specified", async () => {
nock("https://my-awesome-api.fake")
diff --git a/src/useMutate.tsx b/src/useMutate.tsx
index d36b291..11acff2 100644
--- a/src/useMutate.tsx
+++ b/src/useMutate.tsx
@@ -80,7 +80,7 @@ export function useMutate<
typeof arguments[0] === "object" ? arguments[0] : { ...arguments[2], path: arguments[1], verb: arguments[0] };
const context = useContext(Context);
- const { verb, base = context.base, path, queryParams = {}, resolve, pathParams = {} } = props;
+ const { verb, base = context.base, path, queryParams = EMPTY_OBJECT, resolve, pathParams = EMPTY_OBJECT } = props;
const isDelete = verb === "DELETE";
const [state, setState] = useState>({
@@ -93,16 +93,15 @@ export function useMutate<
// Cancel the fetch on unmount
useEffect(() => () => abort(), [abort]);
+ const { pathInlineBodyEncode, queryParamStringifyOptions, requestOptions, localErrorOnly, onMutate } = props;
const mutate = useCallback>(
async (body: TRequestBody, mutateRequestOptions?: MutateRequestOptions) => {
- if (state.error || !state.loading) {
- setState(prevState => ({ ...prevState, loading: true, error: null }));
- }
-
- if (state.loading) {
- // Abort previous requests
- abort();
- }
+ setState(prevState => {
+ if (prevState.loading) {
+ abort();
+ }
+ return { ...prevState, loading: true, error: null };
+ });
const pathStr =
typeof path === "function" ? path(mutateRequestOptions?.pathParams || (pathParams as TPathParams)) : path;
@@ -123,9 +122,7 @@ export function useMutate<
} else if (typeof body === "object") {
options.body = JSON.stringify(body);
} else if (isDelete && body !== undefined) {
- const possiblyEncodedBody = props.pathInlineBodyEncode
- ? props.pathInlineBodyEncode(String(body))
- : String(body);
+ const possiblyEncodedBody = pathInlineBodyEncode ? pathInlineBodyEncode(String(body)) : String(body);
pathParts.push(possiblyEncodedBody);
} else {
@@ -139,14 +136,12 @@ export function useMutate<
pathParts.join("/"),
{ ...context.queryParams, ...queryParams, ...mutateRequestOptions?.queryParams },
{
- queryParamOptions: { ...context.queryParamStringifyOptions, ...props.queryParamStringifyOptions },
+ queryParamOptions: { ...context.queryParamStringifyOptions, ...queryParamStringifyOptions },
},
);
const propsRequestOptions =
- (typeof props.requestOptions === "function"
- ? await props.requestOptions(url, verb, body)
- : props.requestOptions) || {};
+ (typeof requestOptions === "function" ? await requestOptions(url, verb, body) : requestOptions) || {};
const contextRequestOptions =
(typeof context.requestOptions === "function"
@@ -174,7 +169,7 @@ export function useMutate<
loading: false,
});
- if (!props.localErrorOnly && context.onError) {
+ if (!localErrorOnly && context.onError) {
context.onError(error, () => mutate(body, mutateRequestOptions));
}
@@ -223,7 +218,7 @@ export function useMutate<
loading: false,
}));
- if (!props.localErrorOnly && context.onError) {
+ if (!localErrorOnly && context.onError) {
context.onError(error, () => mutate(body), response);
}
@@ -232,14 +227,30 @@ export function useMutate<
setState(prevState => ({ ...prevState, loading: false }));
- if (props.onMutate) {
- props.onMutate(body, data);
+ if (onMutate) {
+ onMutate(body, data);
}
return data;
},
- /* eslint-disable react-hooks/exhaustive-deps */
- [context.base, context.requestOptions, context.resolve, state.error, state.loading, path, abort, getAbortSignal],
+ // 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,
+ ],
);
return {
@@ -255,3 +266,6 @@ export function useMutate<
},
};
}
+
+// Declaring this in order to have a thing with stable identity
+const EMPTY_OBJECT = {};