Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 46 additions & 4 deletions src/lib/fragments/AgGrid.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ 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 prevRowCountRef = useRef(null);
const resetTimeoutRef = useRef(null);

const components = useMemo(
() => ({
Expand Down Expand Up @@ -841,9 +844,11 @@ export function DashAgGrid(props) {
return {
getRows(params) {
getRowsParams.current = params;
if (resettingCount.current) {
return;
}
customSetProps({getRowsRequest: params});
},

destroy() {
getRowsParams.current = null;
},
Expand Down Expand Up @@ -1408,13 +1413,50 @@ 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;
getRowsParams.current.successCallback(rowData, rowCount);

// 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;
params.api.setRowCount(rowCount, false);

resetTimeoutRef.current = setTimeout(() => {
resettingCount.current = false;
const p = getRowsParams.current;
if (p) {
p.successCallback(rowData, rowCount);
}
}, 0);
} else {
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]);

// Handle master detail response
Expand Down
93 changes: 88 additions & 5 deletions tests/test_infinite_scroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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())) == []
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,
):
Comment thread
BSd3v marked this conversation as resolved.
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
Loading