diff --git a/packages/jsapi-components/src/useTableSize.test.ts b/packages/jsapi-components/src/useTableSize.test.ts
index 53a329623e..fc296efcf3 100644
--- a/packages/jsapi-components/src/useTableSize.test.ts
+++ b/packages/jsapi-components/src/useTableSize.test.ts
@@ -1,6 +1,6 @@
import { act, renderHook } from '@testing-library/react-hooks';
import dh from '@deephaven/jsapi-shim';
-import type { Table } from '@deephaven/jsapi-types';
+import type { dh as DhType } from '@deephaven/jsapi-types';
import { TestUtils } from '@deephaven/utils';
import useTableSize from './useTableSize';
import useTableListener from './useTableListener';
@@ -21,7 +21,7 @@ it.each([null, undefined])('should return 0 if no table', table => {
it('should return the size of the given table', () => {
const size = 10;
- const table = TestUtils.createMockProxy
({ size });
+ const table = TestUtils.createMockProxy({ size });
const { result } = renderHook(() => useTableSize(table), { wrapper });
@@ -33,7 +33,7 @@ it('should re-render if dh.Table.EVENT_SIZECHANGED event occurs', () => {
const table = {
addEventListener: jest.fn(),
size: initialSize,
- } as unknown as Table;
+ } as unknown as DhType.Table;
const { result } = renderHook(() => useTableSize(table), { wrapper });
diff --git a/packages/jsapi-components/src/useTableSize.ts b/packages/jsapi-components/src/useTableSize.ts
index ca2187ee08..3a84b7d5e9 100644
--- a/packages/jsapi-components/src/useTableSize.ts
+++ b/packages/jsapi-components/src/useTableSize.ts
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
import { useApi } from '@deephaven/jsapi-bootstrap';
import type { dh } from '@deephaven/jsapi-types';
import { getSize } from '@deephaven/jsapi-utils';
@@ -18,9 +18,11 @@ export function useTableSize(
const dh = useApi();
- useTableListener(table, dh.Table.EVENT_SIZECHANGED, () => {
+ const onSizeChanged = useCallback(() => {
forceRerender(i => i + 1);
- });
+ }, []);
+
+ useTableListener(table, dh.Table.EVENT_SIZECHANGED, onSizeChanged);
return getSize(table);
}
diff --git a/packages/jsapi-components/src/useViewportData.ts b/packages/jsapi-components/src/useViewportData.ts
index fdbbce70f7..a4a222fd20 100644
--- a/packages/jsapi-components/src/useViewportData.ts
+++ b/packages/jsapi-components/src/useViewportData.ts
@@ -1,10 +1,11 @@
-import { useCallback, useEffect, useMemo } from 'react';
+import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { dh } from '@deephaven/jsapi-types';
import {
RowDeserializer,
defaultRowDeserializer,
isClosed,
createOnTableUpdatedHandler,
+ OnTableUpdatedEvent,
} from '@deephaven/jsapi-utils';
import Log from '@deephaven/log';
import { useApi } from '@deephaven/jsapi-bootstrap';
@@ -78,6 +79,8 @@ export function useViewportData({
deserializeRow = defaultRowDeserializer,
reuseItemsOnTableResize = false,
}: UseViewportDataProps): UseViewportDataResult {
+ const currentViewportFirstRowRef = useRef(0);
+
const viewportData = useInitializeViewportData(
table,
reuseItemsOnTableResize
@@ -91,6 +94,8 @@ export function useViewportData({
const setViewport = useCallback(
(firstRow: number) => {
+ currentViewportFirstRowRef.current = firstRow;
+
if (table && !isClosed(table)) {
setPaddedViewport(firstRow);
} else {
@@ -114,20 +119,35 @@ export function useViewportData({
const dh = useApi();
- const onTableUpdated = useMemo(
+ // Store the memoized callback in a ref so that changes to `viewportData`
+ // don't invalidate the memoization of `onTableUpdated`. This prevents
+ // `useTableListener` from unnecessarily re-subscribing to the same event over
+ // and over.
+ const onTableUpdatedRef = useRef<(event: OnTableUpdatedEvent) => void>();
+ onTableUpdatedRef.current = useMemo(
() => createOnTableUpdatedHandler(viewportData, deserializeRow),
[deserializeRow, viewportData]
);
+ const onTableUpdated = useCallback((event: OnTableUpdatedEvent) => {
+ onTableUpdatedRef.current?.(event);
+ }, []);
+
useTableListener(table, dh.Table.EVENT_UPDATED, onTableUpdated);
const size = useTableSize(table);
useEffect(() => {
+ log.debug('Initializing viewport');
+
// Hydrate the viewport with real data. This will fetch data from index
// 0 to the end of the viewport + padding.
setViewport(0);
- }, [table, setViewport, size]);
+ }, [table, setViewport]);
+
+ useEffect(() => {
+ setViewport(currentViewportFirstRowRef.current);
+ }, [setViewport, size]);
const onScroll = useOnScrollOffsetChangeCallback(
itemHeight,