From 00b25d38d606ea2de21178540731ff3b834169a5 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:24:22 -0400 Subject: [PATCH 1/9] fixing issue with infinite scroll and a row count of 0 --- src/lib/fragments/AgGrid.react.js | 20 ++++++- tests/test_infinite_scroll.py | 93 +++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 6f7dad3a..7b889761 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -274,6 +274,7 @@ export function DashAgGrid(props) { const [openGroups, setOpenGroups] = useState({}); const [columnState_push, setColumnState_push] = useState(true); const [rowTransactionState, setRowTransactionState] = useState(null); + const resettingCount = useRef(false); const components = useMemo( () => ({ @@ -839,11 +840,12 @@ export function DashAgGrid(props) { const getDatasource = useCallback(() => { return { + rowCount: undefined, getRows(params) { getRowsParams.current = params; + if (resettingCount.current) return; customSetProps({getRowsRequest: params}); }, - destroy() { getRowsParams.current = null; }, @@ -1412,7 +1414,21 @@ export function DashAgGrid(props) { useEffect(() => { if (isDatasourceLoadedForInfiniteScrolling()) { const {rowData, rowCount} = props.getRowsResponse; - getRowsParams.current.successCallback(rowData, rowCount); + if (getRowsParams.current && 'oldRowCount' in getRowsParams.current) { + if (getRowsParams.current['oldRowCount'] == 0) { + resettingCount.current = true; + getRowsParams.current.api.setRowCount(rowCount, false); + setTimeout(() => { + resettingCount.current = false + getRowsParams.current.successCallback(rowData, rowCount); + }, 0); + } + getRowsParams.current.successCallback(rowData, rowCount); + } else { + getRowsParams.current.successCallback(rowData, rowCount); + } + getRowsParams.current['oldRowCount'] = rowCount; + customSetProps({getRowsResponse: null}); } }, [props.getRowsResponse]); diff --git a/tests/test_infinite_scroll.py b/tests/test_infinite_scroll.py index d34f4e7d..6294c104 100644 --- a/tests/test_infinite_scroll.py +++ b/tests/test_infinite_scroll.py @@ -3,12 +3,12 @@ """ import dash_ag_grid as dag -import dash -from dash import Input, Output, html, dcc, Dash, no_update -from . import utils -import pandas as pd +from dash import Input, Output, html, dcc, Dash, no_update, ctx from dash.testing.wait import until +from . import utils import time +import numpy as np +import pandas as pd def test_is001_infinite_scroll(dash_duo): @@ -247,4 +247,87 @@ def scroll(n): for x in range(8): dash_duo.find_element("#scroll").click() time.sleep(3) # pausing to emulate separation because user inputs - assert list(filter(lambda i: i.get("level") != "WARNING", dash_duo.get_logs())) == [] \ No newline at end of file + assert list(filter(lambda i: i.get("level") != "WARNING", dash_duo.get_logs())) == [] + +def test_is003_infinite_scroll_clear(dash_duo): + app = Dash(__name__) + data = pd.DataFrame( + { + "id": [str(x) for x in range(1000)], + "value": np.random.rand(1000), + } + ) + + clear_button = html.Button("Clear", id="clear") + reset_button = html.Button("Reset", id='reset') + + grid = dag.AgGrid( + columnDefs=[ + {"field": "id"}, + {"field": "value"}, + ], + getRowId="params.data.id", + rowModelType="infinite", + id="grid" + ) + + test_data = [] + + + @app.callback( + Output(grid, "getRowsResponse"), + Input(grid, "getRowsRequest"), + Input(clear_button, "n_clicks"), + Input(reset_button, "n_clicks"), + prevent_initial_call=True, + ) + def update_rfq_grid_rows( + request, + n_clicks_clear, + n_clicks_reset, + ): + if ctx.triggered_id == "clear": + response = { + "rowData": [], "rowCount": 0, + } + return response + + if request is None: + partial_df = data.head(100) + else: + partial_df = data.iloc[request["startRow"] : request["endRow"]] + + response = { + "rowData": partial_df.to_dict("records"), + "rowCount": len(data.index), + } + test_data.append('response') + return response + + app.layout = html.Div( + children=[ + clear_button, + reset_button, + grid, + ] + ) + + dash_duo.start_server(app) + + grid_dom = utils.Grid(dash_duo, 'grid') + grid_dom.wait_for_cell_text(0, 0, "0") + + for x in range(2, 5): + dash_duo.find_element("#clear").click() + until( + lambda: len( + dash_duo.find_elements( + "#grid .ag-center-cols-container > *" + ) + ) + == 0, + timeout=3, + ) + dash_duo.find_element("#reset").click() + grid_dom.wait_for_cell_text(0, 0, "0") + assert x == len(test_data) # make sure the callback was called the expected number of times \ No newline at end of file From 28f743cacc80736980edf3b169813510351117a6 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:25:28 -0400 Subject: [PATCH 2/9] adding changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0252802f..a4bb9f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source D - Added test for `OBJ_MAYBE_FUNCTION_OR_MAP_MAYBE_FUNCTIONS` to test that the value is an object before parsing, - this allows for reused keys to not comply with being an object +### Fixed +- [#454](https://github.com/plotly/dash-ag-grid/pull/454) fixes issue where a rowCount of 0 would cause the grid not to display new data ## [35.2.0] - 2026-04-03 ### Added From 3eaa659801ca52797e393b6b4917a28ccd38ade4 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:28:22 -0400 Subject: [PATCH 3/9] fix for lint --- src/lib/fragments/AgGrid.react.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 7b889761..a82a517f 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -840,10 +840,11 @@ export function DashAgGrid(props) { const getDatasource = useCallback(() => { return { - rowCount: undefined, getRows(params) { getRowsParams.current = params; - if (resettingCount.current) return; + if (resettingCount.current) { + return; + } customSetProps({getRowsRequest: params}); }, destroy() { @@ -1414,20 +1415,26 @@ export function DashAgGrid(props) { useEffect(() => { if (isDatasourceLoadedForInfiniteScrolling()) { const {rowData, rowCount} = props.getRowsResponse; - if (getRowsParams.current && 'oldRowCount' in getRowsParams.current) { - if (getRowsParams.current['oldRowCount'] == 0) { + if ( + getRowsParams.current && + 'oldRowCount' in getRowsParams.current + ) { + if (getRowsParams.current.oldRowCount === 0) { resettingCount.current = true; getRowsParams.current.api.setRowCount(rowCount, false); setTimeout(() => { - resettingCount.current = false - getRowsParams.current.successCallback(rowData, rowCount); + resettingCount.current = false; + getRowsParams.current.successCallback( + rowData, + rowCount + ); }, 0); } getRowsParams.current.successCallback(rowData, rowCount); } else { getRowsParams.current.successCallback(rowData, rowCount); } - getRowsParams.current['oldRowCount'] = rowCount; + getRowsParams.current.oldRowCount = rowCount; customSetProps({getRowsResponse: null}); } From 29e387d9c71548980801e3973678a5fca4b55b25 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:12:43 -0400 Subject: [PATCH 4/9] Update src/lib/fragments/AgGrid.react.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/lib/fragments/AgGrid.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index a82a517f..c623ff2f 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -841,10 +841,10 @@ export function DashAgGrid(props) { const getDatasource = useCallback(() => { return { getRows(params) { - getRowsParams.current = params; if (resettingCount.current) { return; } + getRowsParams.current = params; customSetProps({getRowsRequest: params}); }, destroy() { From 4779acf3d9804e3e180f8efbb158fd7449f6f99f Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:13:05 -0400 Subject: [PATCH 5/9] Update src/lib/fragments/AgGrid.react.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/lib/fragments/AgGrid.react.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index c623ff2f..fe3abac7 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -1429,8 +1429,9 @@ export function DashAgGrid(props) { rowCount ); }, 0); + } else { + getRowsParams.current.successCallback(rowData, rowCount); } - getRowsParams.current.successCallback(rowData, rowCount); } else { getRowsParams.current.successCallback(rowData, rowCount); } From d1538ba05d25f8404d75ebc9e5e62fc990da4079 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:33:18 -0400 Subject: [PATCH 6/9] fixing issues and adjusting the preRowCountRef --- src/lib/fragments/AgGrid.react.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index fe3abac7..5dced0c6 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -275,6 +275,7 @@ export function DashAgGrid(props) { const [columnState_push, setColumnState_push] = useState(true); const [rowTransactionState, setRowTransactionState] = useState(null); const resettingCount = useRef(false); + const prevRowCountRef = useRef(null); const components = useMemo( () => ({ @@ -841,10 +842,10 @@ export function DashAgGrid(props) { const getDatasource = useCallback(() => { return { getRows(params) { + getRowsParams.current = params; if (resettingCount.current) { return; } - getRowsParams.current = params; customSetProps({getRowsRequest: params}); }, destroy() { @@ -1416,26 +1417,20 @@ export function DashAgGrid(props) { if (isDatasourceLoadedForInfiniteScrolling()) { const {rowData, rowCount} = props.getRowsResponse; if ( - getRowsParams.current && - 'oldRowCount' in getRowsParams.current + prevRowCountRef.current !== null && + prevRowCountRef.current === 0 ) { - if (getRowsParams.current.oldRowCount === 0) { - resettingCount.current = true; - getRowsParams.current.api.setRowCount(rowCount, false); - setTimeout(() => { - resettingCount.current = false; - getRowsParams.current.successCallback( - rowData, - rowCount - ); - }, 0); - } else { + resettingCount.current = true; + getRowsParams.current.api.setRowCount(rowCount, false); + setTimeout(() => { + resettingCount.current = false; getRowsParams.current.successCallback(rowData, rowCount); - } + }, 0); } else { getRowsParams.current.successCallback(rowData, rowCount); } - getRowsParams.current.oldRowCount = rowCount; + + prevRowCountRef.current = rowCount; customSetProps({getRowsResponse: null}); } From 8ef0e7fbd2d79dee54de618f0ed3140a0681c1b0 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:28:03 -0400 Subject: [PATCH 7/9] added doc strings and protection for unmounting before timeout is finished --- src/lib/fragments/AgGrid.react.js | 52 ++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 5dced0c6..0d17be1e 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -276,6 +276,7 @@ export function DashAgGrid(props) { const [rowTransactionState, setRowTransactionState] = useState(null); const resettingCount = useRef(false); const prevRowCountRef = useRef(null); + const resetTimeoutRef = useRef(null); const components = useMemo( () => ({ @@ -1412,30 +1413,67 @@ export function DashAgGrid(props) { } }, [props.id]); - // Handle infinite scrolling datasource + // handle getRowsResponse useEffect(() => { - if (isDatasourceLoadedForInfiniteScrolling()) { + if ( + isDatasourceLoadedForInfiniteScrolling() && + !getRowsParams.current + ) { + const params = getRowsParams.current; + const {rowData, rowCount} = props.getRowsResponse; + + // If we were previously at 0 rows, tell ag‑Grid the new count first, + // then defer the successCallback so ag‑Grid has processed setRowCount. + // This avoids an edge case where ag‑Grid ignores the successCallback because it thinks the + // request is already fulfilled, since the row count is >0, but then doesn't render any rows + // because it hasn't processed the new row count yet. + // We do not use purge, reset on the cache or datasource refresh here, + // since those would trigger a new getRows request, which we do not want since we already have the new data + // and just need to get ag‑Grid to process the new row count and render it. if ( prevRowCountRef.current !== null && prevRowCountRef.current === 0 ) { resettingCount.current = true; - getRowsParams.current.api.setRowCount(rowCount, false); - setTimeout(() => { + params.api.setRowCount(rowCount, false); + + resetTimeoutRef.current = setTimeout(() => { resettingCount.current = false; - getRowsParams.current.successCallback(rowData, rowCount); + const p = getRowsParams.current; + if (p) { + p.successCallback(rowData, rowCount); + } }, 0); } else { - getRowsParams.current.successCallback(rowData, rowCount); + params.successCallback(rowData, rowCount); } prevRowCountRef.current = rowCount; - customSetProps({getRowsResponse: null}); } + + return () => { + if (resetTimeoutRef.current) { + clearTimeout(resetTimeoutRef.current); + resetTimeoutRef.current = null; + } + resettingCount.current = false; + }; }, [props.getRowsResponse]); + // ensure any stray timeout cleared on unmount (defensive) + useEffect( + () => () => { + if (resetTimeoutRef.current) { + clearTimeout(resetTimeoutRef.current); + resetTimeoutRef.current = null; + } + resettingCount.current = false; + }, + [] + ); + // Handle master detail response useEffect(() => { if ( From e98fe68bcf16cf656a3302a167f508eb0b35cc43 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:32:01 -0400 Subject: [PATCH 8/9] fixing tyop --- src/lib/fragments/AgGrid.react.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 0d17be1e..71f99f73 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -1415,10 +1415,7 @@ export function DashAgGrid(props) { // handle getRowsResponse useEffect(() => { - if ( - isDatasourceLoadedForInfiniteScrolling() && - !getRowsParams.current - ) { + if (isDatasourceLoadedForInfiniteScrolling() && getRowsParams.current) { const params = getRowsParams.current; const {rowData, rowCount} = props.getRowsResponse; From e8f61c5605c9876973c4d5fb271803ad8d56ec98 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:32:50 -0400 Subject: [PATCH 9/9] removing extra cleanup --- src/lib/fragments/AgGrid.react.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index 71f99f73..dd5ed434 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -1459,18 +1459,6 @@ export function DashAgGrid(props) { }; }, [props.getRowsResponse]); - // ensure any stray timeout cleared on unmount (defensive) - useEffect( - () => () => { - if (resetTimeoutRef.current) { - clearTimeout(resetTimeoutRef.current); - resetTimeoutRef.current = null; - } - resettingCount.current = false; - }, - [] - ); - // Handle master detail response useEffect(() => { if (