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

Commit

Permalink
Support queryParamStringifyOptions on RestfulProvider level
Browse files Browse the repository at this point in the history
  • Loading branch information
ApacheEx authored and fabien0102 committed Jul 20, 2020
1 parent 4452f57 commit 81a4020
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 4 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ export interface RestfulReactProviderProps<T = any> {
* Trigger on each response.
*/
onResponse?: (req: Response) => void;
/**
* Query parameters passed to each request.
*/
queryParams?: { [key: string]: any };
/**
* Query parameter stringify options applied for each request.
*/
queryParamStringifyOptions?: IStringifyOptions;
}

// Usage
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "restful-react",
"version": "14.2.1",
"version": "14.2.2",
"description": "A consistent, declarative way of interacting with RESTful backends, featuring code-generation from Swagger and OpenAPI specs",
"keywords": [
"rest",
Expand Down
7 changes: 7 additions & 0 deletions src/Context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import noop from "lodash/noop";
import * as React from "react";
import { IStringifyOptions } from "qs";
import { ResolveFunction } from "./Get";

export interface RestfulReactProviderProps<TData = any> {
Expand Down Expand Up @@ -48,6 +49,10 @@ export interface RestfulReactProviderProps<TData = any> {
* **Warning:** it's probably not a good idea to put API keys here. Consider headers instead.
*/
queryParams?: { [key: string]: any };
/**
* Query parameter stringify options applied for each request.
*/
queryParamStringifyOptions?: IStringifyOptions;
}

export const Context = React.createContext<Required<RestfulReactProviderProps>>({
Expand All @@ -59,6 +64,7 @@ export const Context = React.createContext<Required<RestfulReactProviderProps>>(
onRequest: noop,
onResponse: noop,
queryParams: {},
queryParamStringifyOptions: {},
});

export interface InjectedProps {
Expand All @@ -82,6 +88,7 @@ export default class RestfulReactProvider<T> extends React.Component<RestfulReac
requestOptions: {},
parentPath: "",
queryParams: value.queryParams || {},
queryParamStringifyOptions: value.queryParamStringifyOptions || {},
...value,
}}
>
Expand Down
89 changes: 89 additions & 0 deletions src/useGet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,95 @@ describe("useGet hook", () => {
expect(children.mock.calls[1][0].loading).toEqual(false);
expect(children.mock.calls[1][0].data).toEqual({ id: 42 });
});

it("should inherit global queryParamStringifyOptions if none specified", async () => {
nock("https://my-awesome-api.fake")
.get("/")
.query(i => {
return i["anArray[]"] === "nice";
})
.reply(200, () => ({ id: 42 }));

const children = jest.fn();
children.mockReturnValue(<div />);

const MyAwesomeComponent: React.FC<{ path: string }> = ({ path }) => {
const params = useGet<{ id: number }, any, { anArray: string[] }>({
path,
queryParams: { anArray: ["nice"] },
});
return children(params);
};

render(
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
<MyAwesomeComponent path="" />
</RestfulProvider>,
);

await wait(() => expect(children).toBeCalledTimes(2));
expect(children.mock.calls[1][0].loading).toEqual(false);
expect(children.mock.calls[1][0].data).toEqual({ id: 42 });
});

it("should override global queryParamStringifyOptions if own queryParamStringifyOptions are specified", async () => {
nock("https://my-awesome-api.fake")
.get("/")
.query(i => {
return i["anArray"] === "foo,bar";
})
.reply(200, () => ({ id: 42 }));

const children = jest.fn();
children.mockReturnValue(<div />);

const MyAwesomeComponent: React.FC<{ path: string }> = ({ path }) => {
const params = useGet<{ id: number }, any, { anArray: string[] }>({
path,
queryParams: { anArray: ["foo", "bar"] },
queryParamStringifyOptions: { arrayFormat: "comma" },
});
return children(params);
};

render(
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
<MyAwesomeComponent path="" />
</RestfulProvider>,
);

await wait(() => expect(children).toBeCalledTimes(2));
expect(children.mock.calls[1][0].loading).toEqual(false);
expect(children.mock.calls[1][0].data).toEqual({ id: 42 });
});

it("should merge global queryParamStringifyOptions if both queryParamStringifyOptions are specified", async () => {
nock("https://my-awesome-api.fake")
.get("/?anArray[]=nice;foo=bar")
.reply(200, () => ({ id: 42 }));

const children = jest.fn();
children.mockReturnValue(<div />);

const MyAwesomeComponent: React.FC<{ path: string }> = ({ path }) => {
const params = useGet<{ id: number }, any, { anArray: string[]; foo: string }>({
path,
queryParams: { anArray: ["nice"], foo: "bar" },
queryParamStringifyOptions: { delimiter: ";" },
});
return children(params);
};

render(
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
<MyAwesomeComponent path="" />
</RestfulProvider>,
);

await wait(() => expect(children).toBeCalledTimes(2));
expect(children.mock.calls[1][0].loading).toEqual(false);
expect(children.mock.calls[1][0].data).toEqual({ id: 42 });
});
});

describe("generation pattern", () => {
Expand Down
13 changes: 11 additions & 2 deletions src/useGet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ async function _fetchData<TData, TError, TQueryParams, TPathParams>(
path,
resolve = (d: any) => d as TData,
queryParams = {},
queryParamStringifyOptions = {},
requestOptions,
pathParams = {},
} = props;
Expand All @@ -121,7 +122,12 @@ async function _fetchData<TData, TError, TQueryParams, TPathParams>(
const signal = getAbortSignal();

const request = new Request(
resolvePath(base, pathStr, { ...context.queryParams, ...queryParams }, props.queryParamStringifyOptions || {}),
resolvePath(
base,
pathStr,
{ ...context.queryParams, ...queryParams },
{ ...context.queryParamStringifyOptions, ...queryParamStringifyOptions },
),
merge({}, contextRequestOptions, propsRequestOptions, { signal }),
);
if (context.onRequest) context.onRequest(request);
Expand Down Expand Up @@ -275,7 +281,10 @@ export function useGet<TData = any, TError = any, TQueryParams = { [key: string]
...context.queryParams,
...props.queryParams,
},
props.queryParamStringifyOptions,
{
...context.queryParamStringifyOptions,
...props.queryParamStringifyOptions,
},
),
cancel: () => {
setState({
Expand Down
92 changes: 92 additions & 0 deletions src/useMutate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,98 @@ describe("useMutate", () => {
});
expect(res).toEqual({ vegan: true });
});

it("should inherit global queryParamStringifyOptions if none specified", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query(i => {
return i["anArray[]"] === "nice";
})
.reply(200, () => ({ vegan: true }));

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(
() =>
useMutate("DELETE", "", {
queryParams: { anArray: ["nice"] },
}),
{
wrapper,
},
);
const res = await result.current.mutate("");

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

it("should override global queryParamStringifyOptions if own queryParamStringifyOptions are specified", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query(i => {
return i["anArray"] === "foo,bar";
})
.reply(200, () => ({ vegan: true }));

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(
() =>
useMutate("DELETE", "", {
queryParams: { anArray: ["foo", "bar"] },
queryParamStringifyOptions: { arrayFormat: "comma" },
}),
{
wrapper,
},
);
const res = await result.current.mutate("");

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

it("should merge global queryParamStringifyOptions if both queryParamStringifyOptions are specified", async () => {
nock("https://my-awesome-api.fake")
.delete("/?anArray[]=nice;foo=bar")
.reply(200, () => ({ vegan: true }));

const wrapper: React.FC = ({ children }) => (
<RestfulProvider queryParamStringifyOptions={{ arrayFormat: "brackets" }} base="https://my-awesome-api.fake">
{children}
</RestfulProvider>
);
const { result } = renderHook(
() =>
useMutate("DELETE", "", {
queryParams: { anArray: ["nice"], foo: "bar" },
queryParamStringifyOptions: { delimiter: ";" },
}),
{
wrapper,
},
);
const res = await result.current.mutate("");

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

describe("Path Params", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/useMutate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export function useMutate<
base,
isDelete ? `${pathStr}/${body}` : pathStr,
{ ...context.queryParams, ...queryParams, ...mutateRequestOptions?.queryParams },
props.queryParamStringifyOptions,
{ ...context.queryParamStringifyOptions, ...props.queryParamStringifyOptions },
),
merge({}, contextRequestOptions, options, propsRequestOptions, mutateRequestOptions, { signal }),
);
Expand Down

0 comments on commit 81a4020

Please sign in to comment.