diff --git a/packages/jsapi-components/src/useTableSize.test.ts b/packages/jsapi-components/src/useTableSize.test.ts index 53a329623..fc296efcf 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 ca2187ee0..3a84b7d5e 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 fdbbce70f..a4a222fd2 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,