Skip to content

Commit

Permalink
Merge pull request #22 from axios-use/feat-options-getResponseItem
Browse files Browse the repository at this point in the history
Feat: `getResponseItem` options (custom data value)
  • Loading branch information
wangcch committed Mar 3, 2024
2 parents a44d1a1 + f6a146c commit 3d2b053
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 16 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ ReactDOM.render(

#### RequestProvider config

| config | type | explain |
| -------------------- | --------------- | ---------------------------------------------------------- |
| instance | object | axios instance |
| cache | object \| false | Customized cache collections. Or close. (**Default on**) |
| cacheKey | function | Global custom formatted cache keys |
| cacheFilter | function | Global callback function to decide whether to cache or not |
| customCreateReqError | function | Custom format error data |
| config | type | default | explain |
| -------------------- | --------------- | ------------------------ | ----------------------------------------------------------- |
| instance | object | axios | axios instance |
| cache | object \| false | \_ttlcache | Customized cache collections. Or close. (**Default on**) |
| cacheKey | function | defaultCacheKeyGenerator | Global custom formatted cache keys |
| cacheFilter | function | - | Global callback function to decide whether to cache or not |
| customCreateReqError | function | - | Custom format error data |
| getResponseItem | function | `(r) => r.data` | custom `data` value. The default value is response['data']. |

### useRequest

Expand Down
25 changes: 22 additions & 3 deletions src/requestContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createContext, useMemo } from "react";
import type { PropsWithChildren } from "react";
import type { AxiosInstance } from "axios";
import type { AxiosInstance, AxiosResponse } from "axios";

import type { RequestError } from "./request";
import type { Cache, CacheKeyFn, CacheFilter } from "./cache";
Expand All @@ -13,6 +13,8 @@ export type RequestContextConfig<T = any, E = any> = {
cacheKey?: CacheKeyFn<T>;
cacheFilter?: CacheFilter<T>;
customCreateReqError?: (err: any) => RequestError<T, any, E>;
/** custom `data` value. @default response['data'] */
getResponseItem?: (res?: any) => unknown;
};

export type RequestContextValue<T = any, E = any> = RequestContextConfig<T, E>;
Expand All @@ -22,6 +24,8 @@ const cache = wrapCache(_ttlcache);
const defaultConfig: RequestContextConfig = {
cache,
cacheKey: defaultCacheKeyGenerator,
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
getResponseItem: (res: AxiosResponse) => res?.data,
};

export const RequestContext = createContext<RequestContextValue>(defaultConfig);
Expand All @@ -37,12 +41,27 @@ export const RequestProvider = <T,>(
cacheKey,
cacheFilter,
customCreateReqError,
getResponseItem,
...rest
} = props;

const providerValue = useMemo(
() => ({ instance, cache, cacheKey, cacheFilter, customCreateReqError }),
[cache, cacheFilter, cacheKey, customCreateReqError, instance],
() => ({
instance,
cache,
cacheKey,
cacheFilter,
customCreateReqError,
getResponseItem,
}),
[
cache,
cacheFilter,
cacheKey,
customCreateReqError,
getResponseItem,
instance,
],
);

return (
Expand Down
15 changes: 10 additions & 5 deletions src/useRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { useMountedState, useRefFn } from "./utils";
export type UseRequestOptions<TRequest extends Request> =
RequestCallbackFn<TRequest> & {
instance?: AxiosInstance;
/** custom returns the value of `data`. @default (r) => r?.data */
getResponseItem?: (res?: any) => unknown;
};

export type UseRequestResult<TRequest extends Request> = [
Expand Down Expand Up @@ -75,11 +77,14 @@ export function useRequest<T extends Request>(
.then((response) => {
removeCancelToken(source.token);

onCompletedRef.current?.(
response.data as Payload<T, true>,
response as Payload<T>,
);
return [response.data, response as Payload<T>] as const;
const _data = (
options?.getResponseItem
? options.getResponseItem(response as Payload<T>)
: RequestConfig.getResponseItem?.(response)
) as Payload<T, true>;

onCompletedRef.current?.(_data, response as Payload<T>);
return [_data, response as Payload<T>] as const;
})
.catch((err: AxiosError<Payload<T>, BodyData<T>>) => {
removeCancelToken(source.token);
Expand Down
3 changes: 2 additions & 1 deletion src/useResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type UseResourceResult<T extends Request> = [

export type UseResourceOptions<T extends Request> = Pick<
RequestContextConfig<Payload<T>>,
"cache" | "cacheFilter" | "instance"
"cache" | "cacheFilter" | "instance" | "getResponseItem"
> &
RequestCallbackFn<T> & {
cacheKey?: CacheKey | CacheKeyFn<T>;
Expand Down Expand Up @@ -145,6 +145,7 @@ export function useResource<T extends Request>(
onCompleted: options?.onCompleted,
onError: options?.onError,
instance: options?.instance,
getResponseItem: options?.getResponseItem,
});
const [state, dispatch] = useReducer(getNextState, {
data: cacheData ?? undefined,
Expand Down
89 changes: 89 additions & 0 deletions tests/useResource.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,95 @@ describe("useResource", () => {
expect(onError).toHaveBeenCalledWith(result.current[0].error);
});
});

it("options: getResponseItem", async () => {
const { result } = renderHook(() =>
useResource(() => ({ url: "/users", method: "GET" }), false, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
getResponseItem: (r) => r?.data?.data,
}),
);
expect(result.current[0].isLoading).toBeFalsy();
expect(result.current[0].data).toBeUndefined();
expect(result.current[0].response).toBeUndefined();

act(() => {
result.current[1]();
});

expect(result.current[0].isLoading).toBeTruthy();
expect(result.current[0].data).toBeUndefined();
expect(result.current[0].response).toBeUndefined();

await waitFor(() => {
expect(result.current[0].isLoading).toBeFalsy();
expect(result.current[0].error).toBeUndefined();
// custom data
expect(result.current[0].data).toStrictEqual(okResponse.data);
expect(result.current[0].response?.data).toStrictEqual(okResponse);
expect(result.current[0].response?.status).toBe(200);
});
});
it("axios config: transformResponse", async () => {
const { result: result01 } = renderHook(() =>
useResource(() => ({
url: "/users",
method: "GET",
transformResponse: () => null,
})),
);
expect(result01.current[0].isLoading).toBeFalsy();
expect(result01.current[0].data).toBeUndefined();
expect(result01.current[0].response).toBeUndefined();

act(() => {
result01.current[1]();
});
await waitFor(() => {
expect(result01.current[0].isLoading).toBeFalsy();
expect(result01.current[0].error).toBeUndefined();
// transformResponse undefined
expect(result01.current[0].data).toBeNull();
expect(result01.current[0].response?.data).toBeNull();
expect(result01.current[0].response?.status).toBe(200);
});
});

it("context: getResponseItem", async () => {
const { result } = originalRenderHook(
() => useResource(() => ({ url: "/users", method: "get" })),
{
wrapper: (props: PropsWithChildren<RequestContextConfig>) => (
<RequestProvider
instance={axios}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
getResponseItem={(r) => r?.data?.data}
{...props}
/>
),
},
);
expect(result.current[0].isLoading).toBeFalsy();
expect(result.current[0].data).toBeUndefined();
expect(result.current[0].response).toBeUndefined();

act(() => {
result.current[1]();
});

expect(result.current[0].isLoading).toBeTruthy();
expect(result.current[0].data).toBeUndefined();
expect(result.current[0].response).toBeUndefined();

await waitFor(() => {
expect(result.current[0].isLoading).toBeFalsy();
expect(result.current[0].error).toBeUndefined();
// custom data
expect(result.current[0].data).toStrictEqual(okResponse.data);
expect(result.current[0].response?.data).toStrictEqual(okResponse);
expect(result.current[0].response?.status).toBe(200);
});
});
});

describe("useResource - cache", () => {
Expand Down

0 comments on commit 3d2b053

Please sign in to comment.