Skip to content

AG Grid Compliance Refactor

An audit of the AG Grid implementation in arda-frontend-app against AG Grid v34 best practices. The codebase contains approximately 1,860 lines of custom code that can be replaced by AG Grid built-in features.

Date: 2026-02-23

SeverityCount
High4
Medium7
Low7

Estimated net reduction: approximately 1,790 lines of custom code.

H1: Select Editors Missing destroy() — Memory Leak

Section titled “H1: Select Editors Missing destroy() — Memory Leak”

Files: ItemTableAGGrid.tsxColorCellEditor, OrderMechanismCellEditor, CardSizeCellEditor, LabelSizeCellEditor, BreadcrumbSizeCellEditor

Event listeners added in afterGuiAttached() are never removed because destroy() is not implemented. Each edit cycle leaks a focus listener on the <select> element.

Recommended fix: Replace all 5 with agSelectCellEditor:

// BEFORE: ~100 lines per editor
class ColorCellEditor {
afterGuiAttached() {
this.selectElement.addEventListener('focus', () => this.selectElement.click());
// listener never removed — no destroy()
}
}
// AFTER: 3-line column definition
{
field: 'color',
cellEditor: 'agSelectCellEditor',
cellEditorParams: { values: ['Red', 'Blue', 'Green', 'Yellow', 'Orange', 'Purple', 'Black', 'White'] }
}

Effort: Low (500 lines removed, 5 column definition updates).

H2: QuickActionsCell Fires N+1 API Requests on Mount

Section titled “H2: QuickActionsCell Fires N+1 API Requests on Mount”

File: columnPresets.tsxQuickActionsCell renderer

Each QuickActionsCell instance fires API requests on mount to fetch card counts. With 50 visible rows, this creates 50 additional API calls on every page load.

Recommended fix: Move card count fetching to the grid data source. Fetch card data alongside item data and pass counts through rowData instead of triggering per-row API calls.

Effort: Medium (requires refactoring data flow).

H3: Select-All Fires N Selection-Changed Events

Section titled “H3: Select-All Fires N Selection-Changed Events”

Files: SelectAllHeaderComponent in columnPresets.tsx, SelectAllHeader in DesktopScanView.tsx

node.setSelected() is called per node, firing one selectionChanged event per row.

Recommended fix (Option A — preferred): Use AG Grid built-in selection:

<AgGridReact
rowSelection={{
mode: 'multiRow',
checkboxes: true,
headerCheckbox: true,
selectAll: 'filtered',
}}
/>

This eliminates 189 lines of custom code.

Recommended fix (Option B — if keeping custom header): Use setNodesSelected for batch operation:

const nodes: IRowNode[] = [];
api.forEachNodeAfterFilterAndSort((node) => nodes.push(node));
api.setNodesSelected({ nodes, newValue: true }); // 1 event instead of N

Effort: Medium (Option A) or Low (Option B).

H4: exportDataAsExcel Calls Enterprise API

Section titled “H4: exportDataAsExcel Calls Enterprise API”

File: ArdaGrid.tsx

Calls gridApi.exportDataAsExcel() but only AllCommunityModule is registered. This throws at runtime.

Recommended fix: Remove exportDataAsExcel from the ArdaGrid API, or replace with gridApi.exportDataAsCsv() (Community module).

Effort: Low.

IDIssueFileFix
M1Module-level lastSelectedRowIndex shared across grid instancescolumnPresets.tsx, DesktopScanView.tsxMove to useRef per grid instance; eliminated by H3 fix
M2headerStyle not a valid ColDef property — silently ignoredcolumnPresets.tsxReplace with headerClass
M3Inline arrow functions as cellRenderer — recreated on every rendercolumnPresets.tsxUse valueFormatter for text-only columns
M4Direct mutation of rowData objectsItemTableAGGrid.tsxUse gridApi.applyTransaction({ update: [updatedItem] })
M5document.querySelector('.ag-theme-arda') matches wrong grid when scan modal is openItemTableAGGrid.tsxUse a React ref to the grid container element
M6QuickActions buttons overflow at 480px responsive breakpointArdaGrid.cssIncrease min-row-height at 480px or clip with overflow hidden
M7afterGuiAttached focus-click loop in select editorsItemTableAGGrid.tsxEliminated by H1 fix
IDIssueLocationFix
L1suppressMultiRangeSelection removed in v33+ArdaGrid.tsxRemove the property
L2suppressSizeToFit deprecated in v31columnPresets.tsxReplace with suppressAutoSize
L3RowNode import should be IRowNode in v34columnPresets.tsxUpdate import
L4rowSelection: 'single' deprecated syntaxArdaGrid.tsxUpdate to { mode: 'singleRow' }
L5SelectAllHeaderComponent typed as anycolumnPresets.tsxAdd proper typing
L6Supplier URL link missing onMouseDown stopPropagationcolumnPresets.tsxAdd handler
L7300ms single-click delay feels sluggishItemTableAGGrid.tsxReduce to 200ms or use AG Grid native click handling

These patterns are correctly implemented and should not change during refactoring:

  1. Typeahead editors implement the full lifecycle including destroy() with setTimeout(() => root.unmount(), 0) — correct for React 19 concurrent mode.
  2. createPortal for modals and typeahead dropdowns correctly escapes AG Grid’s overflow: hidden cells.
  3. stopPropagation on interactive elements consistently prevents unwanted row selection.
  4. isCancelAfterEnd() + wasCancelled flag correctly handles ESC key cancellation.
  5. Column state persistence handles format migration and gracefully handles missing or extra columns.
  6. Draft lifecycle has proper deduplication (draftPromisesMapRef) and concurrent-publish protection (publishingRowsRef).

Nine typeahead editors are copy-paste duplicates differing only in the React component and field name (~130 lines each, ~1,170 lines total). Replace with a factory:

function createTypeaheadEditor(TypeaheadComponent: React.ComponentType<TypeaheadProps>) {
return ({ value, onValueChange, stopEditing }) => {
const handleSelect = (selected: string) => { onValueChange(selected); };
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') stopEditing(true);
};
return (
<TypeaheadComponent
cellEditorMode
initialValue={value}
onSelect={handleSelect}
onKeyDown={handleKeyDown}
/>
);
};
}
// Usage:
{ field: 'supplier', cellEditor: createTypeaheadEditor(SupplierTypeahead) }

Result: ~1,170 lines reduced to ~30 lines plus 9 one-line usages.

Replace cellRenderer with valueFormatter for unitCost, createdAt, minQuantity, orderAmount, orderCost, internalSKU, glCode, and other columns that only format text. No React component mount per cell.

AR3: Migrate Class-Based Editors to React Functional Components

Section titled “AR3: Migrate Class-Based Editors to React Functional Components”

After H1 (select editors replaced) and AR1 (typeahead factory), this migration is essentially complete. AG Grid v34 natively wraps React functional components as cell editors.

  1. H1 + M7: Replace select editors with agSelectCellEditor (memory leak and focus loop)
  2. H3 + M1: Replace custom selection with built-in rowSelection (batch events and module-level state)
  3. AR1: Create typeahead editor factory (largest code reduction)
  4. M3 + AR2: Switch text columns to valueFormatter
  5. H4: Fix or remove Excel export
  6. H2: Refactor QuickActionsCell data fetching (most complex, can be deferred)
  7. M2–M6, L1–L7: Clean up remaining issues

Steps 1–4 are independent and can be done in parallel.