Repository: alibaba/hooks
Pull request #1711 (2022-06-25 11:37:41)

feat(useRequest): add isPolling state
<!--
First of all, thank you for your contribution! 😄

New feature please send a pull request to master branch.
Pull requests will be merged after one of the collaborators approve.
Please makes sure that these forms are filled before submitting your pull request, thank you!
-->

[[中文版模板 / Chinese template](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]

### 🤔 This is a ...

- [x] New feature
- [ ] Bug fix
- [ ] Site / documentation update
- [ ] Demo update
- [ ] TypeScript definition update
- [ ] Bundle size optimization
- [ ] Performance optimization
- [ ] Enhancement feature
- [ ] Internationalization
- [ ] Refactoring
- [ ] Code style optimization
- [ ] Test Case
- [ ] Branch merge
- [ ] Other (about what?)

### 🔗 Related issue link
#1574 

<!--
1. Describe the source of requirement, like related issue link.
-->

### 💡 Background and solution

<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list final API implementation and usage sample if that is a new feature.
-->

### 📝 Changelog
add isPolling Return State.
<!--
Describe changes from the user side, and list all potential break changes or other risks.
--->

| Language   | Changelog |
| ---------- | --------- |
| 🇺🇸 English |           |
| 🇨🇳 Chinese |           |

### ☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

- [x] Doc is updated/provided or not needed
- [x] Demo is updated/provided or not needed
- [x] TypeScript definition is updated/provided or not needed
- [x] Changelog is provided or not needed



Patch:
diff --git a/packages/hooks/src/useRequest/doc/basic/basic.en-US.md b/packages/hooks/src/useRequest/doc/basic/basic.en-US.md
index 96eb1017..4f4d853a 100644
--- a/packages/hooks/src/useRequest/doc/basic/basic.en-US.md
+++ b/packages/hooks/src/useRequest/doc/basic/basic.en-US.md
@@ -148,6 +148,7 @@ const {
 | data         | Data returned by service                                                                                                                                                                | `TData` \| `undefined`                                                |
 | error        | Exception thrown by service                                                                                                                                                             | `Error` \| `undefined`                                                |
 | loading      | Is the service being executed                                                                                                                                                           | `boolean`                                                             |
+| isPolling    | Whether the service is polling                                                                                                                                                          | `boolean`                                                             |
 | params       | An array of parameters for the service being executed. For example, you triggered `run(1, 2, 3)`, then params is equal to `[1, 2, 3]`                                                   | `TParams` \| `[]`                                                     |
 | run          | <ul><li> Manually trigger the execution of the service, and the parameters will be passed to the service</li><li>Automatic handling of exceptions, feedback through `onError`</li></ul> | `(...params : TParams) => void`                                       |
 | runAsync     | The usage is the same as `run`, but it returns a Promise, so you need to handle the exception yourself.                                                                                 | `(...params: TParams) => Promise<TData>`                              |
diff --git a/packages/hooks/src/useRequest/doc/basic/basic.zh-CN.md b/packages/hooks/src/useRequest/doc/basic/basic.zh-CN.md
index f2fdf84e..77a2fbde 100644
--- a/packages/hooks/src/useRequest/doc/basic/basic.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/basic/basic.zh-CN.md
@@ -121,6 +121,7 @@ const {
   loading: boolean,
   data?: TData,
   error?: Error,
+  isPolling: boolean,
   params: TParams || [],
   run: (...params: TParams) => void,
   runAsync: (...params: TParams) => Promise<TData>,
@@ -148,6 +149,7 @@ const {
 | data         | service 返回的数据                                                                                       | `TData` \| `undefined`                                                |
 | error        | service 抛出的异常                                                                                       | `Error` \| `undefined`                                                |
 | loading      | service 是否正在执行                                                                                     | `boolean`                                                             |
+| isPolling    | service 是否正在轮询                                                                                     | `boolean`                                                             |
 | params       | 当次执行的 service 的参数数组。比如你触发了 `run(1, 2, 3)`，则 params 等于 `[1, 2, 3]`                   | `TParams` \| `[]`                                                     |
 | run          | <ul><li> 手动触发 service 执行，参数会传递给 service</li><li>异常自动处理，通过 `onError` 反馈</li></ul> | `(...params: TParams) => void`                                        |
 | runAsync     | 与 `run` 用法一致，但返回的是 Promise，需要自行处理异常。                                                | `(...params: TParams) => Promise<TData>`                              |
diff --git a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx
index dcf8f0fa..a10f3427 100644
--- a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx
+++ b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx
@@ -12,7 +12,7 @@ function getUsername() {
 }
 
 export default () => {
-  const { data, loading, run, cancel } = useRequest(getUsername, {
+  const { data, loading, run, cancel, isPolling } = useRequest(getUsername, {
     pollingInterval: 1000,
     pollingWhenHidden: false,
   });
@@ -26,6 +26,7 @@ export default () => {
       <button type="button" onClick={cancel} style={{ marginLeft: 16 }}>
         stop
       </button>
+      <p>Whether polling is in progress: {String(isPolling)}</p>
     </>
   );
 };
diff --git a/packages/hooks/src/useRequest/doc/polling/demo/pollingError.tsx b/packages/hooks/src/useRequest/doc/polling/demo/pollingError.tsx
index 3e597c80..d55f0d2b 100644
--- a/packages/hooks/src/useRequest/doc/polling/demo/pollingError.tsx
+++ b/packages/hooks/src/useRequest/doc/polling/demo/pollingError.tsx
@@ -13,7 +13,7 @@ function getUsername() {
 }
 
 export default () => {
-  const { data, loading, run, cancel } = useRequest(getUsername, {
+  const { data, loading, run, cancel, isPolling } = useRequest(getUsername, {
     pollingInterval: 1000,
     pollingWhenHidden: false,
     pollingErrorRetryCount: 3,
@@ -32,6 +32,7 @@ export default () => {
       <button type="button" onClick={cancel} style={{ marginLeft: 16 }}>
         stop
       </button>
+      <p>Whether polling is in progress: {String(isPolling)}</p>
     </>
   );
 };
diff --git a/packages/hooks/src/useRequest/doc/polling/polling.en-US.md b/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
index 495b4670..069c7c3d 100644
--- a/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
+++ b/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
@@ -40,11 +40,12 @@ You can experience the effect through the following example.
 
 ### Return
 
-| Property | Description   | Type                                     |
-| -------- | ------------- | ---------------------------------------- |
-| run      | Start polling | `(...params: TParams) => void`           |
-| runAsync | Start polling | `(...params: TParams) => Promise<TData>` |
-| cancel   | Stop polling  | `() => void`                             |
+| Property  | Description                    | Type                                     |
+| --------- | ------------------------------ | ---------------------------------------- |
+| run       | Start polling                  | `(...params: TParams) => void`           |
+| runAsync  | Start polling                  | `(...params: TParams) => Promise<TData>` |
+| cancel    | Stop polling                   | `() => void`                             |
+| isPolling | Whether the service is polling | `boolean`                                |
 
 ### Options
 
diff --git a/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md b/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
index b043adf0..2200238c 100644
--- a/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
@@ -40,11 +40,12 @@ const { data, run, cancel } = useRequest(getUsername, {
 
 ### Return
 
-| 参数     | 说明     | 类型                                     |
-| -------- | -------- | ---------------------------------------- |
-| run      | 启动轮询 | `(...params: TParams) => void`           |
-| runAsync | 启动轮询 | `(...params: TParams) => Promise<TData>` |
-| cancel   | 停止轮询 | `() => void`                             |
+| 参数      | 说明                 | 类型                                     |
+| --------- | -------------------- | ---------------------------------------- |
+| run       | 启动轮询             | `(...params: TParams) => void`           |
+| runAsync  | 启动轮询             | `(...params: TParams) => Promise<TData>` |
+| cancel    | 停止轮询             | `() => void`                             |
+| isPolling | service 是否正在轮询 | `boolean`                                |
 
 ### Options
 
diff --git a/packages/hooks/src/useRequest/src/Fetch.ts b/packages/hooks/src/useRequest/src/Fetch.ts
index a067bdb3..7d89f73c 100644
--- a/packages/hooks/src/useRequest/src/Fetch.ts
+++ b/packages/hooks/src/useRequest/src/Fetch.ts
@@ -8,6 +8,7 @@ export default class Fetch<TData, TParams extends any[]> {
   count: number = 0;
 
   state: FetchState<TData, TParams> = {
+    isPolling: false,
     loading: false,
     params: undefined,
     data: undefined,
@@ -58,6 +59,7 @@ export default class Fetch<TData, TParams extends any[]> {
 
     this.setState({
       loading: true,
+      isPolling: !!this.options.pollingInterval,
       params,
       ...state,
     });
@@ -98,7 +100,10 @@ export default class Fetch<TData, TParams extends any[]> {
       this.options.onFinally?.(params, res, undefined);
 
       if (currentCount === this.count) {
-        this.runPluginHandler('onFinally', params, res, undefined);
+        const { isStopPolling } = this.runPluginHandler('onFinally', params, res, undefined) || {};
+        this.setState({
+          isPolling: !!this.options.pollingInterval && !isStopPolling,
+        });
       }
 
       return res;
@@ -119,7 +124,11 @@ export default class Fetch<TData, TParams extends any[]> {
       this.options.onFinally?.(params, undefined, error);
 
       if (currentCount === this.count) {
-        this.runPluginHandler('onFinally', params, undefined, error);
+        const { isStopPolling } =
+          this.runPluginHandler('onFinally', params, undefined, error) || {};
+        this.setState({
+          isPolling: !!this.options.pollingInterval && !isStopPolling,
+        });
       }
 
       throw error;
@@ -138,6 +147,7 @@ export default class Fetch<TData, TParams extends any[]> {
     this.count += 1;
     this.setState({
       loading: false,
+      isPolling: false,
     });
 
     this.runPluginHandler('onCancel');
diff --git a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts
index 005ec887..deaa29d6 100644
--- a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts
+++ b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts
@@ -58,6 +58,9 @@ const usePollingPlugin: Plugin<any, any[]> = (
         }, pollingInterval);
       } else {
         countRef.current = 0;
+        return {
+          isStopPolling: true,
+        };
       }
     },
     onCancel: () => {
diff --git a/packages/hooks/src/useRequest/src/types.ts b/packages/hooks/src/useRequest/src/types.ts
index 442a2dff..0452c3f0 100644
--- a/packages/hooks/src/useRequest/src/types.ts
+++ b/packages/hooks/src/useRequest/src/types.ts
@@ -8,6 +8,7 @@ export type Subscribe = () => void;
 // for Fetch
 
 export interface FetchState<TData, TParams extends any[]> {
+  isPolling: boolean;
   loading: boolean;
   params?: TParams;
   data?: TData;
@@ -109,6 +110,7 @@ export type Plugin<TData, TParams extends any[]> = {
 // };
 
 export interface Result<TData, TParams extends any[]> {
+  isPolling: boolean;
   loading: boolean;
   data?: TData;
   error?: Error;
diff --git a/packages/hooks/src/useRequest/src/useRequestImplement.ts b/packages/hooks/src/useRequest/src/useRequestImplement.ts
index 12dc931c..86f1bad1 100644
--- a/packages/hooks/src/useRequest/src/useRequestImplement.ts
+++ b/packages/hooks/src/useRequest/src/useRequestImplement.ts
@@ -53,6 +53,7 @@ function useRequestImplement<TData, TParams extends any[]>(
 
   return {
     loading: fetchInstance.state.loading,
+    isPolling: fetchInstance.state.isPolling,
     data: fetchInstance.state.data,
     error: fetchInstance.state.error,
     params: fetchInstance.state.params || [],


Test patch:
diff --git a/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts b/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts
index 168faf50..4389a344 100644
--- a/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts
+++ b/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts
@@ -23,6 +23,7 @@ describe('usePollingPlugin', () => {
       );
     });
     expect(hook.result.current.loading).toEqual(true);
+    expect(hook.result.current.isPolling).toEqual(true);
 
     act(() => {
       jest.runAllTimers();
@@ -47,15 +48,18 @@ describe('usePollingPlugin', () => {
     act(() => {
       hook.result.current.cancel();
     });
+    expect(hook.result.current.isPolling).toEqual(false);
 
     act(() => {
       jest.runAllTimers();
     });
     expect(callback).toHaveBeenCalledTimes(3);
+    expect(hook.result.current.isPolling).toEqual(false);
 
     act(() => {
       hook.result.current.run();
     });
+    expect(hook.result.current.isPolling).toEqual(true);
     act(() => {
       jest.runAllTimers();
     });
@@ -87,6 +91,7 @@ describe('usePollingPlugin', () => {
     });
 
     expect(hook2.result.current.loading).toEqual(true);
+    expect(hook2.result.current.isPolling).toEqual(true);
     expect(errorCallback).toHaveBeenCalledTimes(0);
 
     act(() => {
@@ -113,6 +118,7 @@ describe('usePollingPlugin', () => {
     });
     await hook2.waitForNextUpdate();
     expect(errorCallback).toHaveBeenCalledTimes(4);
+    expect(hook2.result.current.isPolling).toEqual(false);
 
     act(() => {
       jest.runAllTimers();
@@ -122,6 +128,7 @@ describe('usePollingPlugin', () => {
     act(() => {
       hook2.result.current.run();
     });
+    expect(hook2.result.current.isPolling).toEqual(true);
     act(() => {
       jest.runAllTimers();
     });