Skip to content

Commit 3d2b053

Browse files
authored
Merge pull request #22 from axios-use/feat-options-getResponseItem
Feat: `getResponseItem` options (custom data value)
2 parents a44d1a1 + f6a146c commit 3d2b053

File tree

5 files changed

+131
-16
lines changed

5 files changed

+131
-16
lines changed

README.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ ReactDOM.render(
7171

7272
#### RequestProvider config
7373

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

8283
### useRequest
8384

src/requestContext.tsx

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { createContext, useMemo } from "react";
22
import type { PropsWithChildren } from "react";
3-
import type { AxiosInstance } from "axios";
3+
import type { AxiosInstance, AxiosResponse } from "axios";
44

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

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

2731
export const RequestContext = createContext<RequestContextValue>(defaultConfig);
@@ -37,12 +41,27 @@ export const RequestProvider = <T,>(
3741
cacheKey,
3842
cacheFilter,
3943
customCreateReqError,
44+
getResponseItem,
4045
...rest
4146
} = props;
4247

4348
const providerValue = useMemo(
44-
() => ({ instance, cache, cacheKey, cacheFilter, customCreateReqError }),
45-
[cache, cacheFilter, cacheKey, customCreateReqError, instance],
49+
() => ({
50+
instance,
51+
cache,
52+
cacheKey,
53+
cacheFilter,
54+
customCreateReqError,
55+
getResponseItem,
56+
}),
57+
[
58+
cache,
59+
cacheFilter,
60+
cacheKey,
61+
customCreateReqError,
62+
getResponseItem,
63+
instance,
64+
],
4665
);
4766

4867
return (

src/useRequest.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { useMountedState, useRefFn } from "./utils";
2323
export type UseRequestOptions<TRequest extends Request> =
2424
RequestCallbackFn<TRequest> & {
2525
instance?: AxiosInstance;
26+
/** custom returns the value of `data`. @default (r) => r?.data */
27+
getResponseItem?: (res?: any) => unknown;
2628
};
2729

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

78-
onCompletedRef.current?.(
79-
response.data as Payload<T, true>,
80-
response as Payload<T>,
81-
);
82-
return [response.data, response as Payload<T>] as const;
80+
const _data = (
81+
options?.getResponseItem
82+
? options.getResponseItem(response as Payload<T>)
83+
: RequestConfig.getResponseItem?.(response)
84+
) as Payload<T, true>;
85+
86+
onCompletedRef.current?.(_data, response as Payload<T>);
87+
return [_data, response as Payload<T>] as const;
8388
})
8489
.catch((err: AxiosError<Payload<T>, BodyData<T>>) => {
8590
removeCancelToken(source.token);

src/useResource.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export type UseResourceResult<T extends Request> = [
4040

4141
export type UseResourceOptions<T extends Request> = Pick<
4242
RequestContextConfig<Payload<T>>,
43-
"cache" | "cacheFilter" | "instance"
43+
"cache" | "cacheFilter" | "instance" | "getResponseItem"
4444
> &
4545
RequestCallbackFn<T> & {
4646
cacheKey?: CacheKey | CacheKeyFn<T>;
@@ -145,6 +145,7 @@ export function useResource<T extends Request>(
145145
onCompleted: options?.onCompleted,
146146
onError: options?.onError,
147147
instance: options?.instance,
148+
getResponseItem: options?.getResponseItem,
148149
});
149150
const [state, dispatch] = useReducer(getNextState, {
150151
data: cacheData ?? undefined,

tests/useResource.test.tsx

+89
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,95 @@ describe("useResource", () => {
415415
expect(onError).toHaveBeenCalledWith(result.current[0].error);
416416
});
417417
});
418+
419+
it("options: getResponseItem", async () => {
420+
const { result } = renderHook(() =>
421+
useResource(() => ({ url: "/users", method: "GET" }), false, {
422+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
423+
getResponseItem: (r) => r?.data?.data,
424+
}),
425+
);
426+
expect(result.current[0].isLoading).toBeFalsy();
427+
expect(result.current[0].data).toBeUndefined();
428+
expect(result.current[0].response).toBeUndefined();
429+
430+
act(() => {
431+
result.current[1]();
432+
});
433+
434+
expect(result.current[0].isLoading).toBeTruthy();
435+
expect(result.current[0].data).toBeUndefined();
436+
expect(result.current[0].response).toBeUndefined();
437+
438+
await waitFor(() => {
439+
expect(result.current[0].isLoading).toBeFalsy();
440+
expect(result.current[0].error).toBeUndefined();
441+
// custom data
442+
expect(result.current[0].data).toStrictEqual(okResponse.data);
443+
expect(result.current[0].response?.data).toStrictEqual(okResponse);
444+
expect(result.current[0].response?.status).toBe(200);
445+
});
446+
});
447+
it("axios config: transformResponse", async () => {
448+
const { result: result01 } = renderHook(() =>
449+
useResource(() => ({
450+
url: "/users",
451+
method: "GET",
452+
transformResponse: () => null,
453+
})),
454+
);
455+
expect(result01.current[0].isLoading).toBeFalsy();
456+
expect(result01.current[0].data).toBeUndefined();
457+
expect(result01.current[0].response).toBeUndefined();
458+
459+
act(() => {
460+
result01.current[1]();
461+
});
462+
await waitFor(() => {
463+
expect(result01.current[0].isLoading).toBeFalsy();
464+
expect(result01.current[0].error).toBeUndefined();
465+
// transformResponse undefined
466+
expect(result01.current[0].data).toBeNull();
467+
expect(result01.current[0].response?.data).toBeNull();
468+
expect(result01.current[0].response?.status).toBe(200);
469+
});
470+
});
471+
472+
it("context: getResponseItem", async () => {
473+
const { result } = originalRenderHook(
474+
() => useResource(() => ({ url: "/users", method: "get" })),
475+
{
476+
wrapper: (props: PropsWithChildren<RequestContextConfig>) => (
477+
<RequestProvider
478+
instance={axios}
479+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
480+
getResponseItem={(r) => r?.data?.data}
481+
{...props}
482+
/>
483+
),
484+
},
485+
);
486+
expect(result.current[0].isLoading).toBeFalsy();
487+
expect(result.current[0].data).toBeUndefined();
488+
expect(result.current[0].response).toBeUndefined();
489+
490+
act(() => {
491+
result.current[1]();
492+
});
493+
494+
expect(result.current[0].isLoading).toBeTruthy();
495+
expect(result.current[0].data).toBeUndefined();
496+
expect(result.current[0].response).toBeUndefined();
497+
498+
await waitFor(() => {
499+
expect(result.current[0].isLoading).toBeFalsy();
500+
expect(result.current[0].error).toBeUndefined();
501+
// custom data
502+
expect(result.current[0].data).toStrictEqual(okResponse.data);
503+
expect(result.current[0].response?.data).toStrictEqual(okResponse);
504+
expect(result.current[0].response?.status).toBe(200);
505+
});
506+
});
418507
});
419508

420509
describe("useResource - cache", () => {

0 commit comments

Comments
 (0)