Skip to content

Commit 985cc0a

Browse files
authored
Merge pull request #15 from axios-use/feat-refresh-fn
Feat(useResource): return refresh function
2 parents 1407056 + 2c43d62 commit 985cc0a

File tree

7 files changed

+154
-27
lines changed

7 files changed

+154
-27
lines changed

.eslintrc.json

-8
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,6 @@
2929
"react-hooks/rules-of-hooks": "error",
3030
"react-hooks/exhaustive-deps": "warn"
3131
},
32-
"overrides": [
33-
{
34-
"files": ["**/*.test.ts", "**/*.test.tsx"],
35-
"rules": {
36-
"@typescript-eslint/no-unsafe-argument": "off"
37-
}
38-
}
39-
],
4032
"settings": {
4133
"react": {
4234
"pragma": "React",

README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ const [createRequest, { hasPending, cancel }] = useRequest(
162162

163163
```tsx
164164
// js
165-
const [{ data, error, isLoading }, fetch] = useResource((id) => ({
165+
const [{ data, error, isLoading }, fetch, refresh] = useResource((id) => ({
166166
url: `/user/${id}`,
167167
method: "GET",
168168
}));
169169

170170
// tsx
171-
const [reqState, fetch] = useResource((id: string) =>
171+
const [reqState, fetch, refresh] = useResource((id: string) =>
172172
// response.data: Result. AxiosResponse<Result>
173173
request<Result>({
174174
url: `/user/${id}`,
@@ -189,7 +189,12 @@ interface ReqState {
189189
cancel: Canceler;
190190
}
191191

192+
// `options.filter` will not be called
192193
type Fetch = (...args: Parameters<TRequest>) => Canceler;
194+
195+
// 1. Same as `fetch`. But no parameters required. Inherit `useResource` parameters
196+
// 2. Will call `options.filter`
197+
type Refresh = () => Canceler | undefined;
193198
```
194199

195200
The request can also be triggered passing its arguments as dependencies to the _useResource_ hook.

README.zh-CN.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ const [createRequest, { hasPending, cancel }] = useRequest(
162162

163163
```tsx
164164
// js
165-
const [{ data, error, isLoading }, fetch] = useResource((id) => ({
165+
const [{ data, error, isLoading }, fetch, refresh] = useResource((id) => ({
166166
url: `/user/${id}`,
167167
method: "GET",
168168
}));
169169

170170
// tsx
171-
const [reqState, fetch] = useResource((id: string) =>
171+
const [reqState, fetch, refresh] = useResource((id: string) =>
172172
// response.data: Result. AxiosResponse<Result>
173173
request<Result>({
174174
url: `/user/${id}`,
@@ -189,7 +189,12 @@ interface ReqState {
189189
cancel: Canceler;
190190
}
191191

192+
// 不会调用 `options.filter` 函数
192193
type Fetch = (...args: Parameters<TRequest>) => Canceler;
194+
195+
// 1. 与 `fetch` 函数类似, 但是没有传参, 保持使用 `useResource` 声明时传参
196+
// 2. 会调用 `options.filter` 函数进行过滤操作
197+
type Refresh = () => Canceler | undefined;
193198
```
194199

195200
将其参数作为依赖项传递给 `useResource`,根据参数变化自动触发请求

src/useResource.ts

+29-9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type RequestState<TRequest extends Request> = {
3535
export type UseResourceResult<TRequest extends Request> = [
3636
RequestState<TRequest> & { cancel: Canceler },
3737
RequestDispatcher<TRequest>,
38+
() => Canceler | undefined,
3839
];
3940

4041
export type UseResourceOptions<T extends Request> = Pick<
@@ -177,19 +178,34 @@ export function useResource<TRequest extends Request>(
177178
[cacheKey, clear, createRequest, getMountedState],
178179
);
179180

180-
const requestRefFn = useRefFn(request);
181181
const filterRefFn = useRefFn(options?.filter);
182182

183+
const refresh = useCallback(
184+
() => {
185+
const _args = (requestParams || []) as Parameters<TRequest>;
186+
const _filter =
187+
typeof filterRefFn.current === "function"
188+
? filterRefFn.current(..._args)
189+
: true;
190+
if (_filter) {
191+
return request(..._args);
192+
}
193+
194+
return undefined;
195+
},
196+
// eslint-disable-next-line react-hooks/exhaustive-deps
197+
[requestParams, request],
198+
);
199+
200+
const refreshRefFn = useRefFn(refresh);
201+
183202
useEffect(() => {
184203
// eslint-disable-next-line @typescript-eslint/no-empty-function
185204
let canceller: Canceler = () => {};
186205
if (requestParams) {
187-
const filter =
188-
typeof filterRefFn.current === "function"
189-
? filterRefFn.current(...requestParams)
190-
: true;
191-
if (filter) {
192-
canceller = requestRefFn.current(...requestParams);
206+
const _c = refreshRefFn.current();
207+
if (_c) {
208+
canceller = _c;
193209
}
194210
}
195211
return canceller;
@@ -202,7 +218,11 @@ export function useResource<TRequest extends Request>(
202218
clear(message);
203219
};
204220

205-
const result: UseResourceResult<TRequest> = [{ ...state, cancel }, request];
221+
const result: UseResourceResult<TRequest> = [
222+
{ ...state, cancel },
223+
request,
224+
refresh,
225+
];
206226
return result;
207-
}, [state, request, getMountedState, clear]);
227+
}, [state, request, refresh, getMountedState, clear]);
208228
}

tests/useResource.test.tsx

+107-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from "react";
12
import {
23
renderHook,
34
originalRenderHook,
@@ -7,8 +8,9 @@ import {
78
axios,
89
} from "./utils";
910

10-
import type { Resource } from "../src";
11+
import type { Resource, RequestContextConfig } from "../src";
1112
import {
13+
request,
1214
useResource,
1315
RequestProvider,
1416
wrapCache,
@@ -533,7 +535,7 @@ describe("useResource - cache", () => {
533535
),
534536
{
535537
// eslint-disable-next-line react/display-name
536-
wrapper: (props) => (
538+
wrapper: (props: RequestContextConfig) => (
537539
<RequestProvider
538540
instance={axios}
539541
cache={mycache}
@@ -563,7 +565,7 @@ describe("useResource - cache", () => {
563565
),
564566
{
565567
// eslint-disable-next-line react/display-name
566-
wrapper: (props) => (
568+
wrapper: (props: RequestContextConfig) => (
567569
<RequestProvider
568570
instance={axios}
569571
cache={mycache}
@@ -599,7 +601,7 @@ describe("useResource - cache", () => {
599601
),
600602
{
601603
// eslint-disable-next-line react/display-name
602-
wrapper: (props) => (
604+
wrapper: (props: RequestContextConfig) => (
603605
<RequestProvider
604606
instance={axios}
605607
cache={mycache}
@@ -636,7 +638,7 @@ describe("useResource - cache", () => {
636638
() => useResource(() => reqConfig),
637639
{
638640
// eslint-disable-next-line react/display-name
639-
wrapper: (props) => (
641+
wrapper: (props: RequestContextConfig) => (
640642
<RequestProvider instance={axios} cache={mycache} {...props} />
641643
),
642644
},
@@ -655,7 +657,7 @@ describe("useResource - cache", () => {
655657
() => useResource(() => reqConfig, undefined, { cacheKey: customKey }),
656658
{
657659
// eslint-disable-next-line react/display-name
658-
wrapper: (props) => (
660+
wrapper: (props: RequestContextConfig) => (
659661
<RequestProvider instance={axios} cache={mycache} {...props} />
660662
),
661663
},
@@ -807,3 +809,102 @@ describe("useResource - custom instance", () => {
807809
});
808810
});
809811
});
812+
813+
describe("useResource - check types", () => {
814+
const getUserReqConfig = (id: string) =>
815+
request<{ id: string; name: string }>({
816+
url: `/user/${id}`,
817+
method: "get",
818+
});
819+
820+
const callTimesFn = jest.fn();
821+
822+
beforeAll(() => {
823+
mockAdapter.onGet(/\/user\/\w+/).reply((config) => {
824+
const _id = config.url?.replace(/\/user\//, "").split("/")?.[0];
825+
if (_id) {
826+
callTimesFn();
827+
return [200, { id: _id, name: `name${_id}` }];
828+
}
829+
return [404];
830+
});
831+
});
832+
833+
it("check types & refresh func", async () => {
834+
const { result, waitFor, rerender } = renderHook((id: string) =>
835+
useResource(getUserReqConfig, [id], {
836+
filter: (p) => !!p,
837+
}),
838+
);
839+
expect(result.current[0].isLoading).toBeFalsy();
840+
expect(result.current[0].data).toBeUndefined();
841+
expect(result.current[0].response).toBeUndefined();
842+
expect(callTimesFn).toHaveBeenCalledTimes(0);
843+
844+
void act(() => {
845+
result.current[1]("001");
846+
});
847+
848+
expect(result.current[0].isLoading).toBeTruthy();
849+
expect(result.current[0].data).toBeUndefined();
850+
expect(result.current[0].response).toBeUndefined();
851+
expect(callTimesFn).toHaveBeenCalledTimes(1);
852+
853+
await waitFor(() => {
854+
expect(result.current[0].error).toBeUndefined();
855+
expect(result.current[0].isLoading).toBeFalsy();
856+
expect(result.current[0].data?.id).toBe("001");
857+
expect(result.current[0].data?.name).toBe("name001");
858+
expect(callTimesFn).toHaveBeenCalledTimes(1);
859+
});
860+
861+
void act(() => {
862+
result.current[2]();
863+
});
864+
865+
expect(result.current[0].isLoading).toBeFalsy();
866+
expect(result.current[0].data?.id).toBe("001");
867+
expect(result.current[0].data?.name).toBe("name001");
868+
expect(callTimesFn).toHaveBeenCalledTimes(1);
869+
870+
rerender("002");
871+
expect(result.current[0].isLoading).toBeTruthy();
872+
expect(result.current[0].data?.id).toBe("001");
873+
expect(result.current[0].data?.name).toBe("name001");
874+
expect(callTimesFn).toHaveBeenCalledTimes(2);
875+
await waitFor(() => {
876+
expect(result.current[0].isLoading).toBeFalsy();
877+
expect(result.current[0].data?.id).toBe("002");
878+
expect(result.current[0].data?.name).toBe("name002");
879+
expect(callTimesFn).toHaveBeenCalledTimes(2);
880+
});
881+
882+
void act(() => {
883+
result.current[2]();
884+
});
885+
expect(result.current[0].isLoading).toBeTruthy();
886+
expect(result.current[0].data?.id).toBe("002");
887+
expect(result.current[0].data?.name).toBe("name002");
888+
expect(callTimesFn).toHaveBeenCalledTimes(3);
889+
await waitFor(() => {
890+
expect(result.current[0].isLoading).toBeFalsy();
891+
expect(result.current[0].data?.id).toBe("002");
892+
expect(result.current[0].data?.name).toBe("name002");
893+
expect(callTimesFn).toHaveBeenCalledTimes(3);
894+
});
895+
896+
// void act(() => {
897+
// result.current[2]("003");
898+
// });
899+
// expect(result.current[0].isLoading).toBeTruthy();
900+
// expect(result.current[0].data?.id).toBe("002");
901+
// expect(result.current[0].data?.name).toBe("name002");
902+
// expect(callTimesFn).toHaveBeenCalledTimes(4);
903+
// await waitFor(() => {
904+
// expect(result.current[0].isLoading).toBeFalsy();
905+
// expect(result.current[0].data?.id).toBe("003");
906+
// expect(result.current[0].data?.name).toBe("name003");
907+
// expect(callTimesFn).toHaveBeenCalledTimes(4);
908+
// });
909+
});
910+
});

tests/utils.test.ts

+3
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,11 @@ describe("getStrByFn", () => {
118118
expect(getStrByFn((a: string) => a, "demo")).toBe("demo");
119119
expect(getStrByFn((a: number, b: number) => a + b, 1, 2)).toBe(3);
120120
expect(getStrByFn((a: number) => a, 3.1415)).toBe(3.1415);
121+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
121122
expect(getStrByFn((a: number) => a, undefined as any)).toBeUndefined();
123+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
122124
expect(getStrByFn((a: number) => a, null as any)).toBeNull();
125+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
123126
expect(getStrByFn((a: number) => a, false as any)).toBeFalsy();
124127
});
125128
});

tests/utils.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
2+
import React from "react";
23
import type { FC, PropsWithChildren } from "react";
34
import axios from "axios";
45
import MockAdapter from "axios-mock-adapter";

0 commit comments

Comments
 (0)