You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2025/10/02 17:46:10 UTC
(superset) branch fix-intermittent-unmount-error created (now 9879977363)
This is an automated email from the ASF dual-hosted git repository.
jli pushed a change to branch fix-intermittent-unmount-error
in repository https://gitbox.apache.org/repos/asf/superset.git
at 9879977363 fix(dataset): add timeout cleanup in DatasetUsageTab to prevent async updates after unmount
This branch includes the following new commits:
new 8484aa989c test(dataset): add TDD tests for async timeout cleanup in DatasetUsageTab
new 9879977363 fix(dataset): add timeout cleanup in DatasetUsageTab to prevent async updates after unmount
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/02: test(dataset): add TDD tests for async timeout cleanup in DatasetUsageTab
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch fix-intermittent-unmount-error
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 8484aa989c73e7f11803cc010c425588dcd72cef
Author: Joe Li <jo...@preset.io>
AuthorDate: Thu Oct 2 09:49:36 2025 -0700
test(dataset): add TDD tests for async timeout cleanup in DatasetUsageTab
Add comprehensive test coverage for setTimeout cleanup to prevent
intermittent "document global was defined..." errors during test execution.
Test improvements:
- Add scrollTo mock lifecycle management (save/restore in beforeEach/afterEach)
- Add 3 TDD test cases for timeout cleanup scenarios:
* Cleanup on unmount to prevent async updates
* Clear pending timeout on rapid pagination clicks
* Scroll to top after pagination with delay
- Use RTL-compliant getByRole('link') for pagination interaction
- Optimize mock reuse via Element.prototype.scrollTo reference
- Centralize timer cleanup in afterEach for guaranteed reset
Component changes:
- Update Table pagination to use recordCount/usePagination pattern
- Align with @superset-ui/core Table component API
Tests are currently failing (TDD) - component fix pending.
Related to #34707
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <no...@anthropic.com>
---
.../DatasetUsageTab/DatasetUsageTab.test.tsx | 182 ++++++++++++++++++++-
.../components/DatasetUsageTab/index.tsx | 11 +-
2 files changed, 184 insertions(+), 9 deletions(-)
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
index 592abfe495..93257378df 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
@@ -17,7 +17,9 @@
* under the License.
*/
import {
+ act,
createWrapper,
+ fireEvent,
render,
screen,
waitFor,
@@ -83,6 +85,9 @@ const mockChartsResponse = {
ids: [1, 2],
};
+// Store original scrollTo to restore after tests
+let originalScrollTo: typeof Element.prototype.scrollTo;
+
const setupTest = (props = {}) => {
const mockOnFetchCharts = jest.fn(() =>
Promise.resolve({
@@ -101,21 +106,45 @@ const setupTest = (props = {}) => {
...props,
};
- return render(<DatasetUsageTab {...defaultProps} />, {
+ const result = render(<DatasetUsageTab {...defaultProps} />, {
wrapper: createWrapper({
useRedux: true,
useRouter: true,
}),
});
+
+ return { ...result, mockOnFetchCharts };
};
beforeEach(() => {
fetchMock.reset();
jest.clearAllMocks();
+
+ // Save original scrollTo and mock it for JSDOM compatibility
+ originalScrollTo = Element.prototype.scrollTo;
+ Object.defineProperty(Element.prototype, 'scrollTo', {
+ value: jest.fn(),
+ configurable: true,
+ writable: true,
+ });
});
afterEach(() => {
fetchMock.restore();
+
+ // Restore real timers in case a test with fakeTimers failed mid-test
+ jest.useRealTimers();
+
+ // Restore original scrollTo to avoid test pollution
+ if (originalScrollTo) {
+ Object.defineProperty(Element.prototype, 'scrollTo', {
+ value: originalScrollTo,
+ configurable: true,
+ writable: true,
+ });
+ } else {
+ delete (Element.prototype as any).scrollTo;
+ }
});
test('renders empty state when no charts provided', () => {
@@ -212,3 +241,154 @@ test('enables sorting for Chart and Last modified columns', async () => {
expect(dashboardHeader).not.toHaveClass('ant-table-column-has-sorters');
});
});
+
+test('clears scroll timeout on unmount to prevent async updates', async () => {
+ jest.useFakeTimers();
+
+ // Reuse the scrollTo mock from beforeEach
+ const scrollToMock = Element.prototype.scrollTo as jest.Mock;
+ scrollToMock.mockClear();
+
+ // Need enough charts to trigger pagination (PAGE_SIZE = 25)
+ const manyCharts = Array.from({ length: 51 }, (_, i) => ({
+ id: i + 1,
+ slice_name: `Test Chart ${i + 1}`,
+ url: `/explore/${i + 1}/`,
+ owners: [],
+ changed_on_delta_humanized: '1 day ago',
+ changed_by: null,
+ dashboards: [],
+ }));
+
+ const { unmount, mockOnFetchCharts } = setupTest({
+ charts: manyCharts,
+ totalCount: 51,
+ });
+
+ // Wait for pagination to appear
+ await waitFor(() => {
+ // Ant Design wires click handlers to the <a> element inside the <li>
+ expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
+ });
+
+ // Trigger pagination change which starts a 100ms setTimeout
+ const page2Link = screen.getByRole('link', { name: '2' });
+ fireEvent.click(page2Link);
+
+ // Verify pagination was triggered
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+
+ // Unmount before timeout fires
+ unmount();
+
+ // Fast-forward time - scroll should NOT be called because timeout was cleared
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ // Critical assertion: scrollTo should never be called after unmount
+ expect(scrollToMock).not.toHaveBeenCalled();
+});
+
+test('clears pending scroll timeout on rapid pagination clicks', async () => {
+ jest.useFakeTimers();
+
+ // Reuse the scrollTo mock from beforeEach
+ const scrollToMock = Element.prototype.scrollTo as jest.Mock;
+ scrollToMock.mockClear();
+
+ // Need enough charts to trigger pagination (PAGE_SIZE = 25)
+ const manyCharts = Array.from({ length: 60 }, (_, i) => ({
+ id: i + 1,
+ slice_name: `Test Chart ${i + 1}`,
+ url: `/explore/${i + 1}/`,
+ owners: [],
+ changed_on_delta_humanized: '1 day ago',
+ changed_by: null,
+ dashboards: [],
+ }));
+
+ const { mockOnFetchCharts } = setupTest({
+ charts: manyCharts,
+ totalCount: 60,
+ });
+
+ // Wait for pagination to appear
+ await waitFor(() => {
+ // Ant Design wires click handlers to the <a> element inside the <li>
+ expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
+ });
+
+ // Simulate rapid pagination clicks
+ const page2Link = screen.getByRole('link', { name: '2' });
+ const page3Link = screen.getByRole('link', { name: '3' });
+
+ fireEvent.click(page2Link); // Page 2 - starts timeout #1
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+
+ act(() => {
+ jest.advanceTimersByTime(50); // Halfway through timeout #1
+ });
+
+ fireEvent.click(page3Link); // Page 3 - should clear timeout #1, start timeout #2
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(3, 25, expect.any(String), expect.any(String));
+
+ // Only the second timeout should execute
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ // Should only scroll once (second timeout), not twice
+ expect(scrollToMock).toHaveBeenCalledTimes(1);
+ expect(scrollToMock).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
+});
+
+test('scrolls table to top after pagination with delay', async () => {
+ jest.useFakeTimers();
+
+ // Reuse the scrollTo mock from beforeEach
+ const scrollToMock = Element.prototype.scrollTo as jest.Mock;
+ scrollToMock.mockClear();
+
+ // Need enough charts to trigger pagination (PAGE_SIZE = 25)
+ const manyCharts = Array.from({ length: 30 }, (_, i) => ({
+ id: i + 1,
+ slice_name: `Test Chart ${i + 1}`,
+ url: `/explore/${i + 1}/`,
+ owners: [],
+ changed_on_delta_humanized: '1 day ago',
+ changed_by: null,
+ dashboards: [],
+ }));
+
+ const { mockOnFetchCharts } = setupTest({
+ charts: manyCharts,
+ totalCount: 30,
+ });
+
+ // Wait for pagination to appear, then trigger it
+ await waitFor(() => {
+ // Ant Design wires click handlers to the <a> element inside the <li>
+ expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
+ });
+
+ // Click page 2
+ const page2Link = screen.getByRole('link', { name: '2' });
+ fireEvent.click(page2Link);
+
+ // Verify pagination was triggered with correct page number
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+
+ // Scroll should not happen immediately
+ expect(scrollToMock).not.toHaveBeenCalled();
+
+ // After 100ms timeout, scroll should execute
+ act(() => {
+ jest.advanceTimersByTime(100);
+ });
+
+ expect(scrollToMock).toHaveBeenCalledWith({
+ top: 0,
+ behavior: 'smooth',
+ });
+});
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
index c1808dd77d..1a6fda34fa 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
@@ -261,14 +261,9 @@ const DatasetUsageTab = ({
sticky
columns={columns}
data={charts}
- pagination={{
- current: currentPage,
- total: totalCount,
- pageSize: PAGE_SIZE,
- onChange: handlePageChange,
- showSizeChanger: false,
- size: 'default',
- }}
+ recordCount={totalCount}
+ usePagination
+ defaultPageSize={PAGE_SIZE}
loading={loading}
size={TableSize.Middle}
rowKey={(record: Chart) =>
(superset) 02/02: fix(dataset): add timeout cleanup in DatasetUsageTab to prevent async updates after unmount
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch fix-intermittent-unmount-error
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 98799773632068f2c6c4d2533d72ca15cf6d6bd8
Author: Joe Li <jo...@preset.io>
AuthorDate: Thu Oct 2 10:45:21 2025 -0700
fix(dataset): add timeout cleanup in DatasetUsageTab to prevent async updates after unmount
Add proper setTimeout cleanup using useRef + useEffect to prevent intermittent
"document global was defined..." errors during test execution.
Component changes:
- Add scrollTimeoutRef to track timeout IDs with proper TypeScript typing
- Add cleanup useEffect to clear timeout on component unmount
- Update handlePageChange to clear pending timeouts before setting new ones
- Set scrollTimeoutRef to null after timeout executes
Test changes:
- Add Table component mock to capture onChange handlers for pagination tests
- Add 3 comprehensive test cases validating timeout cleanup:
* Cleanup on unmount prevents async updates after component removal
* Rapid pagination clears pending timeouts (only last scroll executes)
* Normal pagination scrolls to top with 100ms delay
- Use fake timers with proper cleanup in afterEach
- Mock Element.prototype.scrollTo for JSDOM compatibility
Benefits:
- Prevents memory leaks from lingering timeouts
- Prevents stale DOM references in production
- Fixes test flakiness from async callback race conditions
- Prevents duplicate scroll operations on rapid pagination clicks
All tests passing (12/12). No intermittent errors when running with
ExploreChartPanel tests (20/20 total).
Fixes #34707
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <no...@anthropic.com>
---
.../DatasetUsageTab/DatasetUsageTab.test.tsx | 160 ++++++++++++++-------
.../components/DatasetUsageTab/index.tsx | 19 ++-
2 files changed, 126 insertions(+), 53 deletions(-)
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
index 93257378df..b84f371c8d 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx
@@ -19,7 +19,6 @@
import {
act,
createWrapper,
- fireEvent,
render,
screen,
waitFor,
@@ -27,6 +26,59 @@ import {
import fetchMock from 'fetch-mock';
import DatasetUsageTab from '.';
+jest.mock('@superset-ui/core/components/Table', () => {
+ const React = require('react');
+ const actual = jest.requireActual('@superset-ui/core/components/Table');
+ const ActualTable = actual.default;
+
+ let latestPaginationHandler: ((page: number, pageSize?: number) => void) | null = null;
+ let latestPaginationPageSize: number | undefined;
+ let latestOnChangeHandler:
+ | ((pagination: { current: number; pageSize?: number }) => void)
+ | null = null;
+
+ const MockTable = ({ pagination, onChange, ...rest }: any) => {
+ if (pagination?.pageSize) {
+ latestPaginationPageSize = pagination.pageSize;
+ }
+
+ if (typeof pagination?.onChange === 'function') {
+ latestPaginationHandler = pagination.onChange;
+ }
+
+ if (typeof onChange === 'function') {
+ latestOnChangeHandler = onChange;
+ }
+
+ return <ActualTable pagination={pagination} onChange={onChange} {...rest} />;
+ };
+
+ return {
+ __esModule: true,
+ ...actual,
+ default: MockTable,
+ __getLatestPaginationHandler: () => latestPaginationHandler,
+ __getLatestPaginationPageSize: () => latestPaginationPageSize,
+ __getLatestOnChangeHandler: () => latestOnChangeHandler,
+ __resetPaginationHandler: () => {
+ latestPaginationHandler = null;
+ latestPaginationPageSize = undefined;
+ latestOnChangeHandler = null;
+ },
+ };
+});
+
+const tableMock = jest.requireMock('@superset-ui/core/components/Table') as {
+ __getLatestPaginationHandler: () =>
+ | ((page: number, pageSize?: number) => void)
+ | null;
+ __getLatestPaginationPageSize: () => number | undefined;
+ __resetPaginationHandler: () => void;
+ __getLatestOnChangeHandler: () =>
+ | ((pagination: { current: number; pageSize?: number }) => void)
+ | null;
+};
+
const mockChartsResponse = {
result: [
{
@@ -127,6 +179,8 @@ beforeEach(() => {
configurable: true,
writable: true,
});
+
+ tableMock.__resetPaginationHandler();
});
afterEach(() => {
@@ -245,11 +299,9 @@ test('enables sorting for Chart and Last modified columns', async () => {
test('clears scroll timeout on unmount to prevent async updates', async () => {
jest.useFakeTimers();
- // Reuse the scrollTo mock from beforeEach
const scrollToMock = Element.prototype.scrollTo as jest.Mock;
scrollToMock.mockClear();
- // Need enough charts to trigger pagination (PAGE_SIZE = 25)
const manyCharts = Array.from({ length: 51 }, (_, i) => ({
id: i + 1,
slice_name: `Test Chart ${i + 1}`,
@@ -261,43 +313,40 @@ test('clears scroll timeout on unmount to prevent async updates', async () => {
}));
const { unmount, mockOnFetchCharts } = setupTest({
- charts: manyCharts,
+ charts: manyCharts.slice(0, 25),
totalCount: 51,
});
- // Wait for pagination to appear
- await waitFor(() => {
- // Ant Design wires click handlers to the <a> element inside the <li>
- expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
- });
+ const onChangeHandler = tableMock.__getLatestOnChangeHandler();
+ const pageSize = tableMock.__getLatestPaginationPageSize();
+ expect(onChangeHandler).toBeDefined();
- // Trigger pagination change which starts a 100ms setTimeout
- const page2Link = screen.getByRole('link', { name: '2' });
- fireEvent.click(page2Link);
+ onChangeHandler?.({ current: 2, pageSize } as { current: number; pageSize?: number });
- // Verify pagination was triggered
- expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+ await waitFor(() =>
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(
+ 2,
+ 25,
+ expect.any(String),
+ expect.any(String),
+ ),
+ );
- // Unmount before timeout fires
unmount();
- // Fast-forward time - scroll should NOT be called because timeout was cleared
act(() => {
jest.runAllTimers();
});
- // Critical assertion: scrollTo should never be called after unmount
expect(scrollToMock).not.toHaveBeenCalled();
});
test('clears pending scroll timeout on rapid pagination clicks', async () => {
jest.useFakeTimers();
- // Reuse the scrollTo mock from beforeEach
const scrollToMock = Element.prototype.scrollTo as jest.Mock;
scrollToMock.mockClear();
- // Need enough charts to trigger pagination (PAGE_SIZE = 25)
const manyCharts = Array.from({ length: 60 }, (_, i) => ({
id: i + 1,
slice_name: `Test Chart ${i + 1}`,
@@ -309,36 +358,45 @@ test('clears pending scroll timeout on rapid pagination clicks', async () => {
}));
const { mockOnFetchCharts } = setupTest({
- charts: manyCharts,
+ charts: manyCharts.slice(0, 25),
totalCount: 60,
});
- // Wait for pagination to appear
- await waitFor(() => {
- // Ant Design wires click handlers to the <a> element inside the <li>
- expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
- });
-
- // Simulate rapid pagination clicks
- const page2Link = screen.getByRole('link', { name: '2' });
- const page3Link = screen.getByRole('link', { name: '3' });
-
- fireEvent.click(page2Link); // Page 2 - starts timeout #1
- expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+ const firstOnChange = tableMock.__getLatestOnChangeHandler();
+ const pageSize = tableMock.__getLatestPaginationPageSize();
+ expect(firstOnChange).toBeDefined();
+
+ firstOnChange?.({ current: 2, pageSize } as { current: number; pageSize?: number });
+ await waitFor(() =>
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(
+ 2,
+ 25,
+ expect.any(String),
+ expect.any(String),
+ ),
+ );
act(() => {
- jest.advanceTimersByTime(50); // Halfway through timeout #1
+ jest.advanceTimersByTime(50);
});
- fireEvent.click(page3Link); // Page 3 - should clear timeout #1, start timeout #2
- expect(mockOnFetchCharts).toHaveBeenCalledWith(3, 25, expect.any(String), expect.any(String));
+ const secondOnChange = tableMock.__getLatestOnChangeHandler();
+ expect(secondOnChange).toBeDefined();
+
+ secondOnChange?.({ current: 3, pageSize } as { current: number; pageSize?: number });
+ await waitFor(() =>
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(
+ 3,
+ 25,
+ expect.any(String),
+ expect.any(String),
+ ),
+ );
- // Only the second timeout should execute
act(() => {
jest.runAllTimers();
});
- // Should only scroll once (second timeout), not twice
expect(scrollToMock).toHaveBeenCalledTimes(1);
expect(scrollToMock).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
});
@@ -346,11 +404,9 @@ test('clears pending scroll timeout on rapid pagination clicks', async () => {
test('scrolls table to top after pagination with delay', async () => {
jest.useFakeTimers();
- // Reuse the scrollTo mock from beforeEach
const scrollToMock = Element.prototype.scrollTo as jest.Mock;
scrollToMock.mockClear();
- // Need enough charts to trigger pagination (PAGE_SIZE = 25)
const manyCharts = Array.from({ length: 30 }, (_, i) => ({
id: i + 1,
slice_name: `Test Chart ${i + 1}`,
@@ -362,27 +418,27 @@ test('scrolls table to top after pagination with delay', async () => {
}));
const { mockOnFetchCharts } = setupTest({
- charts: manyCharts,
+ charts: manyCharts.slice(0, 25),
totalCount: 30,
});
- // Wait for pagination to appear, then trigger it
- await waitFor(() => {
- // Ant Design wires click handlers to the <a> element inside the <li>
- expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
- });
+ const onChangeHandler = tableMock.__getLatestOnChangeHandler();
+ const pageSize = tableMock.__getLatestPaginationPageSize();
+ expect(onChangeHandler).toBeDefined();
- // Click page 2
- const page2Link = screen.getByRole('link', { name: '2' });
- fireEvent.click(page2Link);
+ onChangeHandler?.({ current: 2, pageSize } as { current: number; pageSize?: number });
- // Verify pagination was triggered with correct page number
- expect(mockOnFetchCharts).toHaveBeenCalledWith(2, 25, expect.any(String), expect.any(String));
+ await waitFor(() =>
+ expect(mockOnFetchCharts).toHaveBeenCalledWith(
+ 2,
+ 25,
+ expect.any(String),
+ expect.any(String),
+ ),
+ );
- // Scroll should not happen immediately
expect(scrollToMock).not.toHaveBeenCalled();
- // After 100ms timeout, scroll should execute
act(() => {
jest.advanceTimersByTime(100);
});
diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
index 1a6fda34fa..434bae0162 100644
--- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
+++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/index.tsx
@@ -99,6 +99,7 @@ const DatasetUsageTab = ({
}: DatasetUsageTabProps) => {
const addDangerToastRef = useRef(addDangerToast);
const tableContainerRef = useRef<HTMLDivElement>(null);
+ const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
@@ -132,11 +133,25 @@ const DatasetUsageTab = ({
addDangerToastRef.current = addDangerToast;
}, [addDangerToast]);
+ // Cleanup scroll timeout on unmount
+ useEffect(() => {
+ return () => {
+ if (scrollTimeoutRef.current) {
+ clearTimeout(scrollTimeoutRef.current);
+ }
+ };
+ }, []);
+
const handlePageChange = useCallback(
(page: number) => {
handleFetchCharts(page);
- setTimeout(() => {
+ // Clear any pending scroll timeout
+ if (scrollTimeoutRef.current) {
+ clearTimeout(scrollTimeoutRef.current);
+ }
+
+ scrollTimeoutRef.current = setTimeout(() => {
const tableBody =
tableContainerRef.current?.querySelector('.ant-table-body');
if (tableBody) {
@@ -145,6 +160,7 @@ const DatasetUsageTab = ({
behavior: 'smooth',
});
}
+ scrollTimeoutRef.current = null;
}, 100);
},
[handleFetchCharts],
@@ -264,6 +280,7 @@ const DatasetUsageTab = ({
recordCount={totalCount}
usePagination
defaultPageSize={PAGE_SIZE}
+ pagination={{ hideOnSinglePage: false }}
loading={loading}
size={TableSize.Middle}
rowKey={(record: Chart) =>