Skip to content
This repository has been archived by the owner on Nov 11, 2023. It is now read-only.

Commit

Permalink
add support for passing queryParams in mutate method
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbansal authored and fabien0102 committed Apr 12, 2020
1 parent 2628bae commit 617a3e0
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 43 deletions.
21 changes: 16 additions & 5 deletions src/Mutate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ export interface States<TData, TError> {
error?: GetState<TData, TError>["error"];
}

export type MutateMethod<TData, TRequestBody> = (
export interface MutateRequestOptions<TQueryParams> extends RequestInit {
/**
* Query parameters
*/
queryParams?: TQueryParams;
}

export type MutateMethod<TData, TRequestBody, TQueryParams> = (
data: TRequestBody,
mutateRequestOptions?: RequestInit,
mutateRequestOptions?: MutateRequestOptions<TQueryParams>,
) => Promise<TData>;

/**
Expand Down Expand Up @@ -75,7 +82,11 @@ export interface MutateProps<TData, TError, TQueryParams, TRequestBody> {
*
* @param actions - a key/value map of HTTP verbs, aliasing destroy to DELETE.
*/
children: (mutate: MutateMethod<TData, TRequestBody>, states: States<TData, TError>, meta: Meta) => React.ReactNode;
children: (
mutate: MutateMethod<TData, TRequestBody, TQueryParams>,
states: States<TData, TError>,
meta: Meta,
) => React.ReactNode;
/**
* Callback called after the mutation is done.
*
Expand Down Expand Up @@ -126,7 +137,7 @@ class ContextlessMutate<TData, TError, TQueryParams, TRequestBody> extends React
this.abortController.abort();
}

public mutate = async (body: TRequestBody, mutateRequestOptions?: RequestInit) => {
public mutate = async (body: TRequestBody, mutateRequestOptions?: MutateRequestOptions<TQueryParams>) => {
const {
__internal_hasExplicitBase,
base,
Expand Down Expand Up @@ -156,7 +167,7 @@ class ContextlessMutate<TData, TError, TQueryParams, TRequestBody> extends React

// We use ! because it's in defaultProps
if (Object.keys(this.props.queryParams!).length) {
url += `?${qs.stringify(this.props.queryParams)}`;
url += `?${qs.stringify({ ...this.props.queryParams, ...mutateRequestOptions?.queryParams })}`;
}
return url;
};
Expand Down
86 changes: 57 additions & 29 deletions src/useMutate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { renderHook } from "@testing-library/react-hooks";
import "isomorphic-fetch";
import nock from "nock";
import React from "react";
import { RestfulProvider, useMutate } from ".";
import { RestfulProvider, useMutate, UseMutateProps } from ".";
import { Omit } from "./useGet";
import { UseMutateProps } from "./useMutate";

describe("useMutate", () => {
// Mute console.error -> https://github.com/kentcdodds/react-testing-library/issues/281
Expand Down Expand Up @@ -188,6 +187,58 @@ describe("useMutate", () => {
expect(res).toEqual({ vegan: true });
});

it("should merge with the provider's query parameters if both specified", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query({
cheese: "yucky",
meat: "omg amazing",
})
.reply(200, { vegan: "confused" });

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParams={{ meat: "omg amazing" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", "", { queryParams: { cheese: "yucky" } }), {
wrapper,
});
const res = await result.current.mutate("");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ vegan: "confused" });
});

it("should override query parameters if specified in mutate method", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query({
cheese: "yucky",
meat: "omg amazing",
})
.reply(200, { vegan: "confused" });

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParams={{ meat: "omg amazing" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", "", { queryParams: { cheese: "chucky" } }), {
wrapper,
});
const res = await result.current.mutate("", { queryParams: { cheese: "yucky" } });

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ vegan: "confused" });
});

it("should parse the querystring regarding the options", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
Expand Down Expand Up @@ -219,32 +270,6 @@ describe("useMutate", () => {
});
});

it("should merge with the provider's query parameters if both specified", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query({
cheese: "yucky",
meat: "omg amazing",
})
.reply(200, { vegan: "confused" });

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParams={{ meat: "omg amazing" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", "", { queryParams: { cheese: "yucky" } }), {
wrapper,
});
const res = await result.current.mutate("");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ vegan: "confused" });
});

describe("POST", () => {
it("should set loading to true after a call", async () => {
nock("https://my-awesome-api.fake")
Expand Down Expand Up @@ -466,7 +491,10 @@ describe("useMutate", () => {
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(
() => useMutate<{ id: number }, unknown, {}, {}>("POST", "", { resolve: data => ({ id: data.id * 2 }) }),
() =>
useMutate<{ id: number }, unknown, {}, {}>("POST", "", {
resolve: (data: any) => ({ id: data.id * 2 }),
}),
{ wrapper },
);
const res = await result.current.mutate({});
Expand Down
18 changes: 9 additions & 9 deletions src/useMutate.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import merge from "lodash/merge";
import { useCallback, useContext, useEffect, useState } from "react";
import { Context } from "./Context";
import { MutateMethod, MutateState } from "./Mutate";
import { MutateMethod, MutateState, MutateRequestOptions } from "./Mutate";
import { Omit, resolvePath, UseGetProps } from "./useGet";
import { processResponse } from "./util/processResponse";
import { useAbort } from "./useAbort";
Expand All @@ -21,33 +21,33 @@ export interface UseMutateProps<TData, TQueryParams, TRequestBody>
onMutate?: (body: TRequestBody, data: TData) => void;
}

export interface UseMutateReturn<TData, TError, TRequestBody> extends MutateState<TData, TError> {
export interface UseMutateReturn<TData, TError, TRequestBody, TQueryParams> extends MutateState<TData, TError> {
/**
* Cancel the current fetch
*/
cancel: () => void;
/**
* Call the mutate endpoint
*/
mutate: MutateMethod<TData, TRequestBody>;
mutate: MutateMethod<TData, TRequestBody, TQueryParams>;
}

export function useMutate<TData = any, TError = any, TQueryParams = { [key: string]: any }, TRequestBody = any>(
props: UseMutateProps<TData, TQueryParams, TRequestBody>,
): UseMutateReturn<TData, TError, TRequestBody>;
): UseMutateReturn<TData, TError, TRequestBody, TQueryParams>;

export function useMutate<TData = any, TError = any, TQueryParams = { [key: string]: any }, TRequestBody = any>(
verb: UseMutateProps<TData, TQueryParams, TRequestBody>["verb"],
path: string,
props?: Omit<UseMutateProps<TData, TQueryParams, TRequestBody>, "path" | "verb">,
): UseMutateReturn<TData, TError, TRequestBody>;
): UseMutateReturn<TData, TError, TRequestBody, TQueryParams>;

export function useMutate<
TData = any,
TError = any,
TQueryParams = { [key: string]: any },
TRequestBody = any
>(): UseMutateReturn<TData, TError, TRequestBody> {
>(): UseMutateReturn<TData, TError, TRequestBody, TQueryParams> {
const props: UseMutateProps<TData, TQueryParams, TRequestBody> =
typeof arguments[0] === "object" ? arguments[0] : { ...arguments[2], path: arguments[1], verb: arguments[0] };

Expand All @@ -65,8 +65,8 @@ export function useMutate<
// Cancel the fetch on unmount
useEffect(() => () => abort(), [abort]);

const mutate = useCallback<MutateMethod<TData, TRequestBody>>(
async (body: TRequestBody, mutateRequestOptions?: RequestInit) => {
const mutate = useCallback<MutateMethod<TData, TRequestBody, TQueryParams>>(
async (body: TRequestBody, mutateRequestOptions?: MutateRequestOptions<TQueryParams>) => {
if (state.error || !state.loading) {
setState(prevState => ({ ...prevState, loading: true, error: null }));
}
Expand Down Expand Up @@ -99,7 +99,7 @@ export function useMutate<
resolvePath(
base,
isDelete ? `${path}/${body}` : path,
{ ...context.queryParams, ...queryParams },
{ ...context.queryParams, ...queryParams, ...mutateRequestOptions?.queryParams },
props.queryParamStringifyOptions,
),
merge({}, contextRequestOptions, options, propsRequestOptions, mutateRequestOptions, { signal }),
Expand Down

0 comments on commit 617a3e0

Please sign in to comment.