[Bug]: cellRenderer never invoked on initial paint when rowData arrives asynchronously (cells blank until refreshCells)
Link to reproducible scenario
I was not able to attach a Plunker/CodeSandbox link (the template's required field) — creating an
account on a third-party sandbox host wasn't an option for me. Instead, below is a complete,
self-contained HTML file that reproduces it with no build step and no local install: save it as
repro.html, open it in a browser, and read the heading. It loads AG Grid from jsDelivr. Happy to
move it into a sandbox if a maintainer prefers.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ag-grid-community.min.js"></script>
</head>
<body>
<h3 id="verdict">running…</h3>
<div id="grid" class="ag-theme-quartz" style="height:250px"></div>
<button onclick="api.refreshCells({force:true});report()">refreshCells({force:true})</button>
<script>
let rendererCalls = 0;
agGrid.ModuleRegistry.registerModules([agGrid.AllCommunityModule]);
const api = agGrid.createGrid(document.getElementById('grid'), {
rowModelType: 'clientSide',
columnDefs: [
{ field: 'plain' },
{ field: 'custom', cellRenderer: (p) => { rendererCalls++; return 'rendered:' + p.value; } },
],
onGridReady: (p) => {
// Data arrives asynchronously, as it would from a real request.
setTimeout(() => p.api.setGridOption('rowData',
Array.from({length:10}, (_,i) => ({ plain:'row '+i, custom:'value '+i }))), 200);
},
});
function report() {
const cells = [...document.querySelectorAll('.ag-cell')].filter(c => c.getAttribute('col-id')==='custom');
document.getElementById('verdict').textContent =
`rendererCalls=${rendererCalls} | custom cells=${cells.length} blank=${cells.filter(c=>!c.textContent.trim()).length}`;
}
setTimeout(report, 2000);
</script>
</body>
</html>Describe the bug
Steps to reproduce
- Open the file above.
- Read the heading after it settles (~2s).
- Click refreshCells({force:true}) and read the heading again.
Actual behaviour
rendererCalls=0 | custom cells=10 blank=10 <- initial paint
rendererCalls=10 | custom cells=10 blank=0 <- after refreshCells({force:true})The cellRenderer is never invoked for the initial render of rows that arrive asynchronously.
The column renders as completely empty cells. The plain column beside it renders normally, so rows
and values are present — only the custom-rendered column is blank.
api.getCellRendererInstances({columns:['custom']}) returns [] during this window.
There is no exception and no console warning, which makes it easy to ship: a date column just looks empty, and an action column renders no button at all, so the feature reads as missing rather than broken.
Any forced refresh afterwards fixes it permanently — the same renderer then runs correctly, so the renderer itself is fine; it is only skipped on the first paint.
Expected behaviour
The cellRenderer is invoked for the rows delivered by the first rowData / first Server-Side block,
and the cells render without needing a manual refreshCells.
More information
Scope, from testing each variable independently rather than assuming:
- Both row models.
clientSide(above) andserverSide— with SSRM, neitherfirstDataRenderednorstoreRefreshedever fires at all, so there is no natural lifecycle hook to hang a workaround on;modelUpdatedis the only event that fires once real data has landed. - Both renderer shapes — a plain function (above) and an
ICellRendererCompclass. - Community and Enterprise — reproduced on
ag-grid-community(no licence involved) and onag-grid-enterprise, so it is not a licensing/trial-path artefact. - Not a module-registration problem — reproduced with the full
AllCommunityModuleregistered, and with an explicit module list; all module identifiers verified present on the global before registering. - Not new in 36.x — also reproduces on
[email protected]. - Timing matters: the repro delays
setRowDataby 200ms to mimic a real async fetch. Setting row data synchronously in the same tick does not show it.
Guess at why this may be under-reported: it seems to need the vanilla-JS createGrid path plus
asynchronously-arriving data. The framework wrappers may not hit it, and a synchronous demo dataset
does not.
Workaround we are shipping, in case it helps anyone finding this issue: hook modelUpdated once and
call api.refreshCells({ force: true, suppressFlash: true }). force: true is required — an
unforced refresh is a no-op because the underlying value never changed. It does not re-raise
modelUpdated, so there is no refresh loop.
Version
36.1.0 (also reproduced on 36.0.2 and 35.3.1)
Does the issue occur for a specific framework only?
JavaScript
Is the issue only observable on a specific browser?
No — observed on Chromium.
Source: ag-grid/ag-grid