Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(useResource): asyncReq option. (Control the return value of the request) #2

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const [createRequest, { hasPending, cancel }] = useRequest(
| options.onError | function | This function is passed an `RequestError` object |
| options.instance | `AxiosInstance` | Customize the Axios instance of the current item |
| options.getResponseItem | function | custom returns the value of `data`(index 0). |
| options.asyncReq | boolean | Control the return value of the request |

```ts
// js
Expand Down Expand Up @@ -184,6 +185,8 @@ type ReqState = ComputedRef<{

// `options.filter` will not be called
type Fetch = (...args: Parameters<T>) => Canceler;
// if `options.asyncReq` is `true`
type Fetch = (...args: Parameters<T>) => Promise<[Payload<T>, AxiosResponse]>;

// 1. Same as `fetch`. But no parameters required. Inherit `useResource` parameters
// 2. Will call `options.filter`
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": "@axios-use/vue",
"version": "0.2.5",
"version": "0.3.0-alpha.1",
"description": "A Vue composition utilities for Axios.",
"type": "module",
"main": "lib/index.cjs",
Expand Down
4 changes: 4 additions & 0 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export type RequestDispatcher<T extends Request> = (
...args: Parameters<T>
) => Canceler;

export type RequestAsyncFunc<T extends Request> = (
...args: Parameters<T>
) => Promise<readonly [Payload<T, true>, Payload<T>]>;

/**
* Normalize the error response returned from `@axios-use/vue`
*/
Expand Down
49 changes: 36 additions & 13 deletions src/useResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
BodyData,
RequestError,
Request,
RequestAsyncFunc,
RequestDispatcher,
RequestCallbackFn,
} from "./request";
Expand All @@ -29,25 +30,27 @@ export type UseResourceResultState<T extends Request> = ComputedRef<
RequestState<T>
>;

export type UseResourceResult<T extends Request> = [
export type UseResourceResult<T extends Request, A extends boolean = false> = [
/** Response data group */
UseResourceResultState<T>,
/** A function that enables you to re-execute the request. And pass in new variables */
RequestDispatcher<T>,
A extends true ? RequestAsyncFunc<T> : RequestDispatcher<T>,
/** A function that enables you to re-execute the request. Keep the latest variables */
() => Canceler | undefined,
/** A function that cancel the request */
Canceler,
];

export type UseResourceOptions<T extends Request> = Pick<
RequestConfigType,
"instance" | "getResponseItem"
> &
export type UseResourceOptions<
T extends Request,
A extends boolean = false,
> = Pick<RequestConfigType, "instance" | "getResponseItem"> &
RequestCallbackFn<T> & {
/** Conditional Fetching */
filter?: (...args: Parameters<T>) => boolean;
defaultState?: RequestState<T>;
/** Control the return value of the request */
asyncReq?: A;
};

function getDefaultStateLoading<T extends Request>(
Expand Down Expand Up @@ -87,11 +90,11 @@ export type RequestDepsParameters<T extends Request> =
| FullRefArrayItem<Parameters<T>>
| ComputedRef<Parameters<T>>;

export function useResource<T extends Request>(
export function useResource<T extends Request, A extends boolean = false>(
fn: T,
requestParams?: RequestDepsParameters<T> | false,
options?: UseResourceOptions<T>,
): UseResourceResult<T> {
options?: UseResourceOptions<T, A>,
): UseResourceResult<T, A> {
const [createRequest, { clear }] = useRequest(fn, {
onCompleted: options?.onCompleted,
onError: options?.onError,
Expand All @@ -107,7 +110,7 @@ export function useResource<T extends Request>(
...options?.defaultState,
});

const request = (...args: Parameters<T>) => {
const _reqDispatcher = (...args: Parameters<T>) => {
clear(REQUEST_CLEAR_MESSAGE);

const { ready, cancel } = createRequest(...args);
Expand All @@ -126,14 +129,34 @@ export function useResource<T extends Request>(

return cancel;
};
const _reqAsync = async (...args: Parameters<T>) => {
clear(REQUEST_CLEAR_MESSAGE);

const { ready } = createRequest(...args);

try {
dispatch({ type: "start" });
const [data, response] = await ready();
dispatch({ type: "success", data, response });
return [data, response];
} catch (e) {
const error = e as RequestError<Payload<T>, BodyData<T>>;
if (!error.isCancel) {
dispatch({ type: "error", error });
}
throw e;
}
};
const request = (...args: Parameters<T>) =>
options?.asyncReq ? _reqAsync(...args) : _reqDispatcher(...args);

const refresh = () => {
const _args = unrefs(requestParams || []) as Parameters<T>;
const _filter =
typeof options?.filter === "function" ? options.filter(..._args) : true;

if (_filter) {
return request(..._args);
return _reqDispatcher(..._args);
}

return undefined;
Expand All @@ -160,7 +183,7 @@ export function useResource<T extends Request>(
},
);

const _rtnState = computed<RequestState<T>>(() => unref(state));
const rtnState = computed<RequestState<T>>(() => unref(state));

return [_rtnState, request, refresh, cancel];
return [rtnState, request, refresh, cancel] as UseResourceResult<T, A>;
}
15 changes: 15 additions & 0 deletions tests/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ describe("context", () => {
});
});

test("setUseRequestConfig empty options", () => {
const Component = defineComponent({
setup() {
const { instance } = getUseRequestConfig();
expect(instance).toBe(axios);

return () => h("div");
},
});

mount(Component, (app) => {
setUseRequestConfig(app);
});
});

test("setUseRequestConfig (vue 2)", () => {
const Component = defineComponent2({
setup() {
Expand Down
91 changes: 90 additions & 1 deletion tests/useResource.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, expect, test, expectTypeOf } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import type { AxiosResponse } from "axios";
import type { AxiosError, AxiosResponse } from "axios";
import { computed, defineComponent, h, ref, unref, reactive } from "vue";

import type { RequestError } from "../src";
import { useResource, _request } from "../src";
import type { MockDataUserItem } from "./setup/mock-request";
import {
Expand Down Expand Up @@ -348,4 +349,92 @@ describe("useResource", () => {

mount(Component);
});

test("async request function", async () => {
const Component = defineComponent({
setup() {
const params = reactive({ id: "1" });
const [res, req] = useResource(getAPIFuncs(true).user.get, [params], {
filter: (p) => Boolean(p?.id),
asyncReq: true,
});

expect(unref(res).isLoading).toBeTruthy();
expect(unref(res).data).toBeUndefined();
expect(unref(res).response).toBeUndefined();
expect(unref(res).error).toBeUndefined();

const handleChangeId = () => {
req({ id: "2" }).then(([d, r]) => {
expectTypeOf(d).toEqualTypeOf<MockDataUserItem | undefined>();
expectTypeOf(r).toEqualTypeOf<AxiosResponse<MockDataUserItem>>();

const _cur = MOCK_DATA_USER_LIST.find((i) => i.id === "2");
expect(d).toStrictEqual(_cur);
expect(r.data).toStrictEqual(_cur);
});
};
const handleReqErr = () => {
req({ id: "100" })
.then(() => {
expect(1).toBe(2);
})
.catch((e) => {
const _e = e as RequestError<any, any, AxiosError>;
expect(_e.original.response?.status).toBe(404);
});
};

return () =>
h("div", [
h("button", {
"data-t-id": "change_id",
onClick: handleChangeId,
}),
h("button", {
"data-t-id": "req_err",
onClick: handleReqErr,
}),
h(
"div",
{ "data-t-id": "res.data" },
JSON.stringify(res.value.data),
),
h(
"div",
{ "data-t-id": "res.err" },
JSON.stringify(res.value.error),
),
h("div", { "data-t-id": "res.isLoading" }, res.value.isLoading),
h("div", { "data-t-id": "params.id" }, params.id),
]);
},
});

const wrapper = mount(Component);
await flushPromises();
expect(wrapper.get('[data-t-id="res.data"]').text()).toBe(
JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "1")),
);
expect(wrapper.get('[data-t-id="res.err"]').text()).toBe("");
expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false");
expect(wrapper.get('[data-t-id="params.id"]').text()).toBe("1");

wrapper.get('[data-t-id="change_id"]').trigger("click");
await flushPromises();
expect(wrapper.get('[data-t-id="res.data"]').text()).toBe(
JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "2")),
);
expect(wrapper.get('[data-t-id="res.err"]').text()).toBe("");
expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false");
expect(wrapper.get('[data-t-id="params.id"]').text()).toBe("1");

wrapper.get('[data-t-id="req_err"]').trigger("click");
await flushPromises();
expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false");
expect(wrapper.get('[data-t-id="res.data"]').text()).toBe(
JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "2")),
);
expect(wrapper.get('[data-t-id="res.err"]').text()).not.toBe("");
});
});
Loading