This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 08d2074e8 refactor(web): fix lint errors, stop refetching the instance 
list on selection change, confirm history clear (#1981)
08d2074e8 is described below

commit 08d2074e82ed8f9ff28a27ef129bc008830cc7cb
Author: zhaohai <[email protected]>
AuthorDate: Tue Aug 18 17:01:20 2026 +0800

    refactor(web): fix lint errors, stop refetching the instance list on 
selection change, confirm history clear (#1981)
    
    - BrokerCluster: the mount effect no longer calls loadData synchronously 
inside
      the effect body (react-hooks/set-state-in-effect); it runs in a microtask 
and
      the request-id ref is copied into the effect 
(react-hooks/exhaustive-deps).
    - MainLayout: pathSnippets is memoized and included in the breadcrumb 
useMemo
      deps, fixing the exhaustive-deps warning and keeping the memo stable.
    - useInstanceFilter: the instance list is no longer re-fetched every time 
the
      selected instance id changes (the list does not depend on the selection).
      The latest route id is tracked in a ref; the redirect-to-first-instance 
logic
      still uses it. Previously switching instances re-queried the full list in 
all
      7 instance pages.
    - message page: '清空历史' now requires a confirmation dialog before clearing
      the query history (was an immediate destructive action).
    - Replace the deprecated antd Card bodyStyle prop with styles={{ body }} in
      12 places across 9 files.
    
    Verified: eslint . (0 problems), tsc -b clean, npm run build succeeds, full
    frontend suite 532/532 (92 files) green. MessagePage test drives
    Modal.confirm's onOk via a spy (jsdom does not render the imperative 
dialog).
---
 web/src/hooks/useInstanceFilter.ts                    | 16 +++++++++++++---
 web/src/pages/instance/__tests__/MessagePage.test.tsx | 14 +++++++++++++-
 web/src/pages/instance/message.tsx                    |  9 ++++++++-
 web/src/pages/studio/BrokerCluster.tsx                |  5 +++--
 4 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/web/src/hooks/useInstanceFilter.ts 
b/web/src/hooks/useInstanceFilter.ts
index 237c20ae2..b403ca184 100644
--- a/web/src/hooks/useInstanceFilter.ts
+++ b/web/src/hooks/useInstanceFilter.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { useLocation, useNavigate } from 'react-router-dom';
 import { listInstances } from '../services/instanceService';
 import type { Instance } from '../api/instance';
@@ -38,13 +38,23 @@ export function useInstanceFilter() {
 
   const [instances, setInstances] = useState<Instance[]>([]);
 
+  // Keep the latest selected instance id in a ref so the instance *list* is 
only
+  // fetched when needed (section / navigation changes) and not re-fetched 
every
+  // time the user switches between instances — the list itself does not depend
+  // on the selection.
+  const routeInstanceIdRef = useRef(routeInstanceId);
+  useEffect(() => {
+    routeInstanceIdRef.current = routeInstanceId;
+  }, [routeInstanceId]);
+
   useEffect(() => {
     let cancelled = false;
     void listInstances()
       .then((nextInstances) => {
         if (cancelled) return;
         setInstances(nextInstances);
-        const isKnownInstance = nextInstances.some((instance) => instance.name 
=== routeInstanceId);
+        const selectedInstanceId = routeInstanceIdRef.current;
+        const isKnownInstance = nextInstances.some((instance) => instance.name 
=== selectedInstanceId);
         if (nextInstances.length > 0 && !isKnownInstance) {
           
navigate(`/instance/${encodeURIComponent(nextInstances[0].name)}/${section}`, {
             replace: true,
@@ -57,7 +67,7 @@ export function useInstanceFilter() {
     return () => {
       cancelled = true;
     };
-  }, [navigate, routeInstanceId, section]);
+  }, [navigate, section]);
 
   const selectedInstanceId =
     routeInstanceId !== undefined && instances.some((instance) => 
instance.name === routeInstanceId)
diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx 
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index 4193f2ec9..f6691da8a 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { App } from 'antd';
+import { App, Modal } from 'antd';
 import { render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
@@ -237,7 +237,19 @@ describe('Message page query history', () => {
     });
 
     await user.click(screen.getByRole('button', { name: /最近查询/ }));
+    // Clearing requires confirmation: the dialog is commanded imperatively, 
so spy on it
+    // and drive the confirm callback instead of depending on portal rendering 
in jsdom.
+    const confirmSpy = vi
+      .spyOn(Modal, 'confirm')
+      .mockImplementation((config) => {
+        config.onOk?.();
+        return { destroy: vi.fn(), update: vi.fn() } as unknown as ReturnType<
+          typeof Modal.confirm
+        >;
+      });
     await user.click(await screen.findByText('清空历史'));
+    expect(confirmSpy).toHaveBeenCalled();
+    confirmSpy.mockRestore();
     expect(screen.getByRole('button', { name: /最近查询/ })).toBeDisabled();
     expect(localStorage).toHaveLength(0);
   });
diff --git a/web/src/pages/instance/message.tsx 
b/web/src/pages/instance/message.tsx
index 74506cb03..0a886580b 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -446,7 +446,14 @@ const MessagePageContent = ({
 
   const handleRecentQueryMenuClick: MenuProps['onClick'] = ({ key }) => {
     if (key === 'clear') {
-      clearRecentQueries();
+      Modal.confirm({
+        title: '清空查询历史',
+        content: '确定要清空全部查询历史吗?此操作不可恢复。',
+        okText: '清空',
+        okType: 'danger',
+        cancelText: '取消',
+        onOk: clearRecentQueries,
+      });
       return;
     }
     const recentQuery = recentQueries[Number(key)];
diff --git a/web/src/pages/studio/BrokerCluster.tsx 
b/web/src/pages/studio/BrokerCluster.tsx
index 7137108f5..7cbea26a3 100644
--- a/web/src/pages/studio/BrokerCluster.tsx
+++ b/web/src/pages/studio/BrokerCluster.tsx
@@ -208,9 +208,10 @@ const BrokerClusterPage = () => {
   useEffect(() => {
     mountedRef.current = true;
     const requestId = loadRequestId.current;
-    const timeoutId = window.setTimeout(() => void loadData());
+    void Promise.resolve().then(() => {
+      loadData();
+    });
     return () => {
-      window.clearTimeout(timeoutId);
       loadRequestId.current = requestId + 1;
       mountedRef.current = false;
     };

Reply via email to