From d305b03ed2dd543ad3a8fff4b8b9aedebb529831 Mon Sep 17 00:00:00 2001 From: hmilin Date: Fri, 7 Jun 2024 19:36:42 +0800 Subject: [PATCH 1/3] feat(useRequest): return the pollingLoading state when using pollingInterval --- .../src/useRequest/doc/polling/demo/polling.tsx | 8 ++++---- .../src/useRequest/doc/polling/polling.en-US.md | 11 ++++++----- .../src/useRequest/doc/polling/polling.zh-CN.md | 11 ++++++----- packages/hooks/src/useRequest/src/Fetch.ts | 6 ++++-- .../src/useRequest/src/plugins/usePollingPlugin.ts | 13 ++++++++++++- packages/hooks/src/useRequest/src/types.ts | 9 +++++---- .../hooks/src/useRequest/src/useRequestImplement.ts | 1 + 7 files changed, 38 insertions(+), 21 deletions(-) diff --git a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx index dcf8f0facb..bb6294dab0 100644 --- a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx +++ b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx @@ -1,5 +1,5 @@ import { useRequest } from 'ahooks'; -import React from 'react'; +import React, { useState } from 'react'; import Mock from 'mockjs'; function getUsername() { @@ -12,14 +12,14 @@ function getUsername() { } export default () => { - const { data, loading, run, cancel } = useRequest(getUsername, { - pollingInterval: 1000, + const { data, loading, run, cancel, pollingLoading } = useRequest(getUsername, { + pollingInterval: 10000, pollingWhenHidden: false, }); return ( <> -

Username: {loading ? 'Loading' : data}

+

Username: {pollingLoading ? 'PollingLoading' : loading ? 'Loading' : data}

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 56c69db578..3e70feda74 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` | -| cancel | Stop polling | `() => void` | +| Property | Description | Type | +| -------------- | ---------------------------------- | ---------------------------------------- | +| PollingLoading | In the polling request in progress | `boolean` | +| run | Start polling | `(...params: TParams) => void` | +| runAsync | Start polling | `(...params: TParams) => Promise` | +| cancel | Stop polling | `() => void` | ### 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 b124f49d02..8e6aa4f00d 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` | -| cancel | 停止轮询 | `() => void` | +| 参数 | 说明 | 类型 | +| -------------- | -------------------- | ---------------------------------------- | +| PollingLoading | 是否正在进行轮询请求 | `boolean` | +| run | 启动轮询 | `(...params: TParams) => void` | +| runAsync | 启动轮询 | `(...params: TParams) => Promise` | +| cancel | 停止轮询 | `() => void` | ### Options diff --git a/packages/hooks/src/useRequest/src/Fetch.ts b/packages/hooks/src/useRequest/src/Fetch.ts index 73ceee6004..5a4357bbe4 100644 --- a/packages/hooks/src/useRequest/src/Fetch.ts +++ b/packages/hooks/src/useRequest/src/Fetch.ts @@ -99,7 +99,8 @@ export default class Fetch { this.options.onFinally?.(params, res, undefined); if (currentCount === this.count) { - this.runPluginHandler('onFinally', params, res, undefined); + const state = this.runPluginHandler('onFinally', params, res, undefined); + this.setState({ ...state }); } return res; @@ -120,7 +121,8 @@ export default class Fetch { this.options.onFinally?.(params, undefined, error); if (currentCount === this.count) { - this.runPluginHandler('onFinally', params, undefined, error); + const state = this.runPluginHandler('onFinally', params, undefined, error); + this.setState({ ...state }); } throw error; diff --git a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts index bf9a30b47c..98573c3230 100644 --- a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts +++ b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import useUpdateEffect from '../../../useUpdateEffect'; import type { Plugin, Timeout } from '../types'; import isDocumentVisible from '../utils/isDocumentVisible'; @@ -11,6 +11,7 @@ const usePollingPlugin: Plugin = ( const timerRef = useRef(); const unsubscribeRef = useRef<() => void>(); const countRef = useRef(0); + const pollingLoadingRef = useRef(false); const stopPolling = () => { if (timerRef.current) { @@ -32,12 +33,17 @@ const usePollingPlugin: Plugin = ( return { onBefore: () => { stopPolling(); + return { + pollingLoading: pollingLoadingRef.current, + }; }, onError: () => { countRef.current += 1; + pollingLoadingRef.current = false; }, onSuccess: () => { countRef.current = 0; + pollingLoadingRef.current = false; }, onFinally: () => { if ( @@ -49,15 +55,20 @@ const usePollingPlugin: Plugin = ( // if pollingWhenHidden = false && document is hidden, then stop polling and subscribe revisible if (!pollingWhenHidden && !isDocumentVisible()) { unsubscribeRef.current = subscribeReVisible(() => { + pollingLoadingRef.current = true; fetchInstance.refresh(); }); } else { + pollingLoadingRef.current = true; fetchInstance.refresh(); } }, pollingInterval); } else { countRef.current = 0; } + return { + pollingLoading: pollingLoadingRef.current, + }; }, onCancel: () => { stopPolling(); diff --git a/packages/hooks/src/useRequest/src/types.ts b/packages/hooks/src/useRequest/src/types.ts index 442a2dff73..6f84c32f4b 100644 --- a/packages/hooks/src/useRequest/src/types.ts +++ b/packages/hooks/src/useRequest/src/types.ts @@ -12,6 +12,7 @@ export interface FetchState { params?: TParams; data?: TData; error?: Error; + pollingLoading?: boolean; } export interface PluginReturn { @@ -94,10 +95,10 @@ export interface Options { } export type Plugin = { - (fetchInstance: Fetch, options: Options): PluginReturn< - TData, - TParams - >; + ( + fetchInstance: Fetch, + options: Options, + ): PluginReturn; onInit?: (options: Options) => Partial>; }; diff --git a/packages/hooks/src/useRequest/src/useRequestImplement.ts b/packages/hooks/src/useRequest/src/useRequestImplement.ts index e8bcfe45ae..39a53a0dca 100644 --- a/packages/hooks/src/useRequest/src/useRequestImplement.ts +++ b/packages/hooks/src/useRequest/src/useRequestImplement.ts @@ -63,6 +63,7 @@ function useRequestImplement( data: fetchInstance.state.data, error: fetchInstance.state.error, params: fetchInstance.state.params || [], + pollingLoading: fetchInstance.state.pollingLoading, cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)), refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)), refreshAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)), From 20fc3a3849775226a36deb71859b74b05f323aa8 Mon Sep 17 00:00:00 2001 From: hmilin Date: Sat, 8 Jun 2024 15:18:13 +0800 Subject: [PATCH 2/3] fix: reset pollingLoading state onCancel --- packages/hooks/src/useRequest/doc/polling/demo/polling.tsx | 2 +- packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts | 1 + packages/hooks/src/useRequest/src/types.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx index bb6294dab0..02a5bcaa3f 100644 --- a/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx +++ b/packages/hooks/src/useRequest/doc/polling/demo/polling.tsx @@ -13,7 +13,7 @@ function getUsername() { export default () => { const { data, loading, run, cancel, pollingLoading } = useRequest(getUsername, { - pollingInterval: 10000, + pollingInterval: 1000, pollingWhenHidden: false, }); diff --git a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts index 98573c3230..9596ff05d7 100644 --- a/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts +++ b/packages/hooks/src/useRequest/src/plugins/usePollingPlugin.ts @@ -72,6 +72,7 @@ const usePollingPlugin: Plugin = ( }, onCancel: () => { stopPolling(); + pollingLoadingRef.current = false; }, }; }; diff --git a/packages/hooks/src/useRequest/src/types.ts b/packages/hooks/src/useRequest/src/types.ts index 6f84c32f4b..5034185087 100644 --- a/packages/hooks/src/useRequest/src/types.ts +++ b/packages/hooks/src/useRequest/src/types.ts @@ -120,6 +120,7 @@ export interface Result { run: Fetch['run']; runAsync: Fetch['runAsync']; mutate: Fetch['mutate']; + pollingLoading?: boolean; } export type Timeout = ReturnType; From 286932c684108ed92ca4073da634c1df4c2a50ab Mon Sep 17 00:00:00 2001 From: hmilin Date: Sat, 8 Jun 2024 15:19:29 +0800 Subject: [PATCH 3/3] test: add test case for the pollingLoading state --- .../__tests__/usePollingPlugin.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts b/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts index b7a01c4e9f..a067b85452 100644 --- a/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts +++ b/packages/hooks/src/useRequest/__tests__/usePollingPlugin.test.ts @@ -116,4 +116,79 @@ describe('usePollingPlugin', () => { }); await waitFor(() => expect(errorCallback).toHaveBeenCalledTimes(5)); }); + + it('usePollingPlugin pollingInterval=100 pollingWhenHidden=true should return pollingLoading', async () => { + let hook; + const callback = jest.fn(); + act(() => { + hook = setUp( + () => { + callback(); + return request(1); + }, + { + pollingInterval: 100, + pollingWhenHidden: true, + }, + ); + }); + expect(hook.result.current.loading).toBe(true); + // Initial state: pollingLoading should be false + expect(hook.result.current.pollingLoading).toBe(false); + + // first request completed + act(() => { + jest.runAllTimers(); + }); + await waitFor(() => expect(hook.result.current.loading).toBe(false)); + expect(callback).toHaveBeenCalledTimes(1); + + // trigger the next polling request + act(() => { + jest.runAllTimers(); + }); + expect(hook.result.current.loading).toBe(true); + // pollingLoading: true => false + expect(hook.result.current.pollingLoading).toBe(true); + await waitFor(() => expect(hook.result.current.pollingLoading).toBe(false)); + expect(hook.result.current.data).toBe('success'); + expect(callback).toHaveBeenCalledTimes(2); + + act(() => { + jest.runAllTimers(); + }); + // pollingLoading: true => false, synchronized with loading + expect(hook.result.current.loading).toBe(true); + expect(hook.result.current.pollingLoading).toBe(true); + await waitFor(() => expect(hook.result.current.loading).toBe(false)); + expect(hook.result.current.pollingLoading).toBe(false); + expect(hook.result.current.data).toBe('success'); + expect(callback).toHaveBeenCalledTimes(3); + + act(() => { + hook.result.current.cancel(); + }); + expect(hook.result.current.pollingLoading).toBe(false); + + // should be false in manual run + act(() => { + hook.result.current.run(); + }); + expect(hook.result.current.loading).toBe(true); + expect(hook.result.current.pollingLoading).toBe(false); + await waitFor(() => expect(callback).toHaveBeenCalledTimes(4)); + + // first request completed + act(() => { + jest.runAllTimers(); + }); + await waitFor(() => expect(hook.result.current.loading).toBe(false)); + + act(() => { + jest.runAllTimers(); + }); + // pollingLoading: true => false + expect(hook.result.current.pollingLoading).toBe(true); + await waitFor(() => expect(hook.result.current.pollingLoading).toBe(false)); + }); });