Skip to content

Commit c6dcc33

Browse files
authored
Merge pull request #1 from axios-use/feat-options-getResponseItem
Feat: `getResponseItem` options (custom data value)
2 parents 4ad4896 + 1e8a3ca commit c6dcc33

File tree

9 files changed

+309
-76
lines changed

9 files changed

+309
-76
lines changed

README.md

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ import { useRequest, useResource } from "@axios-use/vue";
5454

5555
### Options (optional)
5656

57-
| config | type | default | explain |
58-
| -------- | ------ | ------- | ------------------------------------------------------------- |
59-
| instance | object | `axios` | Axios instance. You can pass your axios with a custom config. |
57+
| config | type | default | explain |
58+
| --------------- | -------- | --------------- | --------------------------------------------------------------------------------------------------------------------- |
59+
| instance | object | `axios` | Axios instance. You can pass your axios with a custom config. |
60+
| getResponseItem | function | `(r) => r.data` | custom `data` value. The default value is response['data']. [PR#1](https://github.com/axios-use/axios-use-vue/pull/1) |
6061

6162
```ts
6263
import axios from "axios";
@@ -83,12 +84,13 @@ Vue.use(AxiosUseVue, { instance: axiosInstance });
8384

8485
### useRequest
8586

86-
| option | type | explain |
87-
| ------------------- | --------------- | ------------------------------------------------ |
88-
| fn | function | get AxiosRequestConfig function |
89-
| options.onCompleted | function | This function is passed the query's result data. |
90-
| options.onError | function | This function is passed an `RequestError` object |
91-
| options.instance | `AxiosInstance` | Customize the Axios instance of the current item |
87+
| option | type | explain |
88+
| ----------------------- | --------------- | ------------------------------------------------ |
89+
| fn | function | get AxiosRequestConfig function |
90+
| options.onCompleted | function | This function is passed the query's result data. |
91+
| options.onError | function | This function is passed an `RequestError` object |
92+
| options.instance | `AxiosInstance` | Customize the Axios instance of the current item |
93+
| options.getResponseItem | function | custom returns the value of `data`(index 0). |
9294

9395
```ts
9496
// js
@@ -136,15 +138,16 @@ const [createRequest, { hasPending, cancel }] = useRequest(
136138

137139
### useResource
138140

139-
| option | type | explain |
140-
| -------------------- | --------------- | ------------------------------------------------------------------- |
141-
| fn | function | get AxiosRequestConfig function |
142-
| parameters | array \| false | `fn` function parameters. effect dependency list |
143-
| options.filter | function | Request filter. if return a falsy value, will not start the request |
144-
| options.defaultState | object | Initialize the state value. `{data, response, error, isLoading}` |
145-
| options.onCompleted | function | This function is passed the query's result data. |
146-
| options.onError | function | This function is passed an `RequestError` object |
147-
| options.instance | `AxiosInstance` | Customize the Axios instance of the current item |
141+
| option | type | explain |
142+
| ----------------------- | --------------- | ------------------------------------------------------------------- |
143+
| fn | function | get AxiosRequestConfig function |
144+
| parameters | array \| false | `fn` function parameters. effect dependency list |
145+
| options.filter | function | Request filter. if return a falsy value, will not start the request |
146+
| options.defaultState | object | Initialize the state value. `{data, response, error, isLoading}` |
147+
| options.onCompleted | function | This function is passed the query's result data. |
148+
| options.onError | function | This function is passed an `RequestError` object |
149+
| options.instance | `AxiosInstance` | Customize the Axios instance of the current item |
150+
| options.getResponseItem | function | custom returns the value of `data`(index 0). |
148151

149152
```ts
150153
// js
@@ -260,6 +263,8 @@ const [reqState] = useResource(
260263
The `request` function allows you to define the response type coming from it. It also helps with creating a good pattern on defining your API calls and the expected results. It's just an identity function that accepts the request config and returns it. Both `useRequest` and `useResource` extract the expected and annotated type definition and resolve it on the `response.data` field.
261264

262265
```ts
266+
import { request } from "@axios-use/vue";
267+
263268
const api = {
264269
getUsers: () => {
265270
return request<Users>({
@@ -285,6 +290,26 @@ const usersRes = await axios(api.getUsers());
285290
const userRes = await axios(api.getUserInfo("ID001"));
286291
```
287292

293+
custom response type. (if you change the response's return value. like `axios.interceptors.response`)
294+
295+
```ts
296+
import { request, _request } from "@axios-use/vue";
297+
298+
const [reqState] = useResource(() => request<DataType>({ url: `/users` }));
299+
// AxiosResponse<DataType>
300+
unref(reqState).response;
301+
// DataType
302+
unref(reqState).data;
303+
304+
// custom response type
305+
const [reqState] = useResource(() => _request<MyWrapper<DataType>>({ url: `/users` }));
306+
// MyWrapper<DataType>
307+
unref(reqState).response;
308+
// MyWrapper<DataType>["data"]. maybe `undefined` type.
309+
// You can use `getResponseItem` to customize the value of `data`
310+
unref(reqState).data;
311+
```
312+
288313
#### createRequestError
289314

290315
The `createRequestError` normalizes the error response. This function is used internally as well. The `isCancel` flag is returned, so you don't have to call **axios.isCancel** later on the promise catch block.

src/context.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { InjectionKey } from "vue";
22
import { getCurrentInstance, inject } from "vue";
33

4-
import type { AxiosInstance } from "axios";
4+
import type { AxiosInstance, AxiosResponse } from "axios";
55
import axios from "axios";
66

77
import type { App } from "../demi";
@@ -12,6 +12,8 @@ const INJECT_INSIDE_WARN_MSG =
1212
export type RequestConfigType = {
1313
/** Axios instance. You can pass your axios with a custom config. */
1414
instance?: AxiosInstance;
15+
/** custom `data` value. @default response['data'] */
16+
getResponseItem?: (res?: any) => unknown;
1517
};
1618

1719
export const AXIOS_USE_VUE_PROVIDE_KEY = Symbol(
@@ -39,16 +41,17 @@ export const setUseRequestConfig = (app: App, options?: RequestConfigType) => {
3941
}
4042
};
4143

44+
const defaultGetResponseData = (res: AxiosResponse) => res?.data;
45+
4246
export const getUseRequestConfig = (): RequestConfigType &
43-
Required<Pick<RequestConfigType, "instance">> => {
47+
Required<Pick<RequestConfigType, "instance" | "getResponseItem">> => {
4448
const _isInside = Boolean(getCurrentInstance());
4549
if (!_isInside) {
4650
console.warn(INJECT_INSIDE_WARN_MSG);
4751
}
4852

49-
const { instance = axios } = _isInside
50-
? inject<RequestConfigType>(AXIOS_USE_VUE_PROVIDE_KEY, {})
51-
: {};
53+
const { instance = axios, getResponseItem = defaultGetResponseData } =
54+
_isInside ? inject<RequestConfigType>(AXIOS_USE_VUE_PROVIDE_KEY, {}) : {};
5255

53-
return { instance };
56+
return { instance, getResponseItem };
5457
};

src/request.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,44 @@ import type {
66
} from "axios";
77
import axios from "axios";
88

9-
export type _ResponseDataItemType<T> = T extends AxiosResponse<infer D1>
10-
? D1
11-
: T extends { data: infer D2 } | { data?: infer D2 }
12-
? D2
13-
: undefined;
14-
15-
export interface Resource<T, D = any, W = AxiosResponse>
16-
extends AxiosRequestConfig<D> {
17-
_payload?: W extends AxiosResponse ? AxiosResponse<T, D> : T;
9+
export interface Resource<
10+
T = AxiosResponse,
11+
D = any,
12+
K1 extends keyof T = never,
13+
K2 extends keyof T[K1] = never,
14+
K3 extends keyof T[K1][K2] = never,
15+
> extends AxiosRequestConfig<D> {
16+
_payload?: T;
17+
_payload_item?: [K3] extends [never]
18+
? [K2] extends [never]
19+
? [K1] extends [never]
20+
? T extends AxiosResponse<infer DD> | { data?: infer DD }
21+
? DD
22+
: undefined
23+
: T[K1]
24+
: T[K1][K2]
25+
: T[K1][K2][K3];
1826
}
1927

20-
export type Request<T = any, D = any, W = any> = (
21-
...args: any[]
22-
) => Resource<T, D, W>;
28+
export type Request<
29+
T = any,
30+
D = any,
31+
K1 extends keyof T = any,
32+
K2 extends keyof T[K1] = any,
33+
K3 extends keyof T[K1][K2] = any,
34+
> = (...args: any[]) => Resource<T, D, K1, K2, K3>;
2335

2436
export type Payload<T extends Request, Check = false> = Check extends true
25-
? _ResponseDataItemType<ReturnType<T>["_payload"]>
37+
? ReturnType<T>["_payload_item"]
38+
: T extends Request<AxiosResponse>
39+
? NonNullable<ReturnType<T>["_payload"]>
2640
: ReturnType<T>["_payload"];
2741
export type BodyData<T extends Request> = ReturnType<T>["data"];
2842

2943
export interface RequestFactory<T extends Request> {
3044
(...args: Parameters<T>): {
3145
cancel: Canceler;
32-
ready: () => Promise<readonly [Payload<T, true>, NonNullable<Payload<T>>]>;
46+
ready: () => Promise<readonly [Payload<T, true>, Payload<T>]>;
3347
};
3448
}
3549

@@ -57,10 +71,7 @@ export type RequestCallbackFn<T extends Request> = {
5771
* A callback function that's called when your request successfully completes with zero errors.
5872
* This function is passed the request's result `data` and `response`.
5973
*/
60-
onCompleted?: (
61-
data: Payload<T, true>,
62-
response: NonNullable<Payload<T>>,
63-
) => void;
74+
onCompleted?: (data: Payload<T, true>, response: Payload<T>) => void;
6475
/**
6576
* A callback function that's called when the request encounters one or more errors.
6677
* This function is passed an `RequestError` object that contains either a networkError object or a `AxiosError`, depending on the error(s) that occurred.
@@ -71,18 +82,21 @@ export type RequestCallbackFn<T extends Request> = {
7182
/**
7283
* For TypeScript type deduction
7384
*/
74-
export function _request<T, D = any, W = false>(
75-
config: AxiosRequestConfig<D>,
76-
): Resource<T, D, W> {
85+
export function _request<
86+
T,
87+
D = any,
88+
K1 extends keyof T = never,
89+
K2 extends keyof T[K1] = never,
90+
K3 extends keyof T[K1][K2] = never,
91+
>(config: AxiosRequestConfig<D>): Resource<T, D, K1, K2, K3> {
7792
return config;
7893
}
7994

8095
/**
8196
* For TypeScript type deduction
8297
*/
83-
export const request = <T, D = any, W = AxiosResponse<T, D>>(
84-
config: AxiosRequestConfig<D>,
85-
) => _request<T, D, W>(config);
98+
export const request = <T, D = any>(config: AxiosRequestConfig<D>) =>
99+
_request<AxiosResponse<T, D>, D>(config);
86100

87101
export function createRequestError<
88102
T = any,

src/useRequest.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { createRequestError } from "./request";
2222

2323
export type UseRequestOptions<T extends Request> = RequestCallbackFn<T> & {
2424
instance?: AxiosInstance;
25+
/** custom returns the value of `data`(index 0). @default (r) => r?.data */
26+
getResponseItem?: (res?: any) => unknown;
2527
};
2628

2729
export type UseRequestResult<T extends Request> = [
@@ -73,11 +75,16 @@ export function useRequest<T extends Request>(
7375
sources.value = [...unref(sources), _source];
7476

7577
return _axiosIns({ ..._config, cancelToken: _source.token })
76-
.then((res: NonNullable<Payload<T>>) => {
78+
.then((res) => {
7779
removeCancelToken(_source.token);
7880

79-
onCompleted?.(res?.data, res);
80-
return [res?.data, res] as const;
81+
const _data = (
82+
options?.getResponseItem
83+
? options.getResponseItem(res as Payload<T>)
84+
: requestConfig.getResponseItem(res)
85+
) as Payload<T, true>;
86+
onCompleted?.(_data, res as Payload<T>);
87+
return [_data, res as Payload<T>] as const;
8188
})
8289
.catch((err: AxiosError<Payload<T>, BodyData<T>>) => {
8390
removeCancelToken(_source.token);

src/useResource.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export type UseResourceResult<T extends Request> = [
4242

4343
export type UseResourceOptions<T extends Request> = Pick<
4444
RequestConfigType,
45-
"instance"
45+
"instance" | "getResponseItem"
4646
> &
4747
RequestCallbackFn<T> & {
4848
/** Conditional Fetching */
@@ -96,6 +96,7 @@ export function useResource<T extends Request>(
9696
onCompleted: options?.onCompleted,
9797
onError: options?.onError,
9898
instance: options?.instance,
99+
getResponseItem: options?.getResponseItem,
99100
});
100101

101102
const [state, dispatch] = useReducer(getNextState, {
@@ -111,19 +112,17 @@ export function useResource<T extends Request>(
111112

112113
const { ready, cancel } = createRequest(...args);
113114

114-
void (async () => {
115-
try {
116-
dispatch({ type: "start" });
117-
const [data, response] = await ready();
118-
115+
dispatch({ type: "start" });
116+
ready()
117+
.then(([data, response]) => {
119118
dispatch({ type: "success", data, response });
120-
} catch (e) {
119+
})
120+
.catch((e) => {
121121
const error = e as RequestError<Payload<T>, BodyData<T>>;
122122
if (!error.isCancel) {
123123
dispatch({ type: "error", error });
124124
}
125-
}
126-
})();
125+
});
127126

128127
return cancel;
129128
};

tests/context.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe("context", () => {
9393
const vm = getCurrentInstance2();
9494

9595
expect(
96-
(vm.proxy as any)._provided[AXIOS_USE_VUE_PROVIDE_KEY as any]
96+
(vm?.proxy as any)._provided[AXIOS_USE_VUE_PROVIDE_KEY as any]
9797
?.instance,
9898
).toBe(mockAxiosIns);
9999

@@ -156,3 +156,44 @@ describe("context", () => {
156156
});
157157
});
158158
});
159+
160+
describe("config - getResponseItem", () => {
161+
test("default value", () => {
162+
const Component = defineComponent({
163+
setup() {
164+
const { getResponseItem } = getUseRequestConfig();
165+
expect(getResponseItem).toBeDefined();
166+
expect(getResponseItem({ data: 1 })).toBe(1);
167+
expect(getResponseItem({})).toBeUndefined();
168+
expect(getResponseItem()).toBeUndefined();
169+
170+
return () => h("div");
171+
},
172+
});
173+
174+
mount(Component);
175+
});
176+
177+
test("custom", () => {
178+
const fn = (r) => ({
179+
o: r,
180+
d: r?.data,
181+
msg: r?.message || r?.statusText || r?.status,
182+
});
183+
const Component = defineComponent({
184+
setup() {
185+
const { getResponseItem } = getUseRequestConfig();
186+
expect(getResponseItem).toBeDefined();
187+
expect(getResponseItem({ data: 1 })).toStrictEqual(fn({ data: 1 }));
188+
expect(getResponseItem({})).toStrictEqual(fn({}));
189+
expect(getResponseItem()).toStrictEqual(fn(undefined));
190+
191+
return () => h("div");
192+
},
193+
});
194+
195+
mount(Component, (app) => {
196+
app.use(AxioUseVue, { getResponseItem: fn });
197+
});
198+
});
199+
});

tests/request.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ describe("type checking", () => {
7373
const rq2 = () => _request<DataType2>({});
7474

7575
it("request", () => {
76-
expectTypeOf(rq0()).toEqualTypeOf<Resource<DataType, any, false>>();
76+
expectTypeOf(rq0()).toEqualTypeOf<Resource<DataType, any>>();
7777
expectTypeOf(rq1()).toEqualTypeOf<
78-
Resource<DataType, any, AxiosResponse<DataType, any>>
78+
Resource<AxiosResponse<DataType>, any, "data">
7979
>();
8080

8181
const c0 = null as unknown as Payload<typeof rq0>;
@@ -84,7 +84,7 @@ describe("type checking", () => {
8484
expectTypeOf(c1).toEqualTypeOf<undefined>();
8585

8686
const c2 = null as unknown as Payload<typeof rq1>;
87-
expectTypeOf(c2).toEqualTypeOf<AxiosResponse<DataType, any> | undefined>();
87+
expectTypeOf(c2).toEqualTypeOf<AxiosResponse<DataType, any>>();
8888
const c3 = null as unknown as Payload<typeof rq1, true>;
8989
expectTypeOf(c3).toEqualTypeOf<DataType | undefined>();
9090

0 commit comments

Comments
 (0)