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
Summary
Section titled “Summary”| Severity | Count |
|---|---|
| High | 4 |
| Medium | 7 |
| Low | 7 |
Estimated net reduction: approximately 1,790 lines of custom code.
High-Risk Issues
Section titled “High-Risk Issues”H1: Select Editors Missing destroy() — Memory Leak
Section titled “H1: Select Editors Missing destroy() — Memory Leak”Files: ItemTableAGGrid.tsx — ColorCellEditor, 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 editorclass 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.tsx — QuickActionsCell 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 NEffort: 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.
Medium-Risk Issues
Section titled “Medium-Risk Issues”| ID | Issue | File | Fix |
|---|---|---|---|
| M1 | Module-level lastSelectedRowIndex shared across grid instances | columnPresets.tsx, DesktopScanView.tsx | Move to useRef per grid instance; eliminated by H3 fix |
| M2 | headerStyle not a valid ColDef property — silently ignored | columnPresets.tsx | Replace with headerClass |
| M3 | Inline arrow functions as cellRenderer — recreated on every render | columnPresets.tsx | Use valueFormatter for text-only columns |
| M4 | Direct mutation of rowData objects | ItemTableAGGrid.tsx | Use gridApi.applyTransaction({ update: [updatedItem] }) |
| M5 | document.querySelector('.ag-theme-arda') matches wrong grid when scan modal is open | ItemTableAGGrid.tsx | Use a React ref to the grid container element |
| M6 | QuickActions buttons overflow at 480px responsive breakpoint | ArdaGrid.css | Increase min-row-height at 480px or clip with overflow hidden |
| M7 | afterGuiAttached focus-click loop in select editors | ItemTableAGGrid.tsx | Eliminated by H1 fix |
Low-Risk Issues
Section titled “Low-Risk Issues”| ID | Issue | Location | Fix |
|---|---|---|---|
| L1 | suppressMultiRangeSelection removed in v33+ | ArdaGrid.tsx | Remove the property |
| L2 | suppressSizeToFit deprecated in v31 | columnPresets.tsx | Replace with suppressAutoSize |
| L3 | RowNode import should be IRowNode in v34 | columnPresets.tsx | Update import |
| L4 | rowSelection: 'single' deprecated syntax | ArdaGrid.tsx | Update to { mode: 'singleRow' } |
| L5 | SelectAllHeaderComponent typed as any | columnPresets.tsx | Add proper typing |
| L6 | Supplier URL link missing onMouseDown stopPropagation | columnPresets.tsx | Add handler |
| L7 | 300ms single-click delay feels sluggish | ItemTableAGGrid.tsx | Reduce to 200ms or use AG Grid native click handling |
Patterns to Preserve
Section titled “Patterns to Preserve”These patterns are correctly implemented and should not change during refactoring:
- Typeahead editors implement the full lifecycle including
destroy()withsetTimeout(() => root.unmount(), 0)— correct for React 19 concurrent mode. createPortalfor modals and typeahead dropdowns correctly escapes AG Grid’soverflow: hiddencells.stopPropagationon interactive elements consistently prevents unwanted row selection.isCancelAfterEnd()+wasCancelledflag correctly handles ESC key cancellation.- Column state persistence handles format migration and gracefully handles missing or extra columns.
- Draft lifecycle has proper deduplication (
draftPromisesMapRef) and concurrent-publish protection (publishingRowsRef).
Architecture Recommendations
Section titled “Architecture Recommendations”AR1: Generic Typeahead Editor Factory
Section titled “AR1: Generic Typeahead Editor Factory”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.
AR2: valueFormatter for Text-Only Columns
Section titled “AR2: valueFormatter for Text-Only Columns”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.
Suggested Implementation Order
Section titled “Suggested Implementation Order”- H1 + M7: Replace select editors with
agSelectCellEditor(memory leak and focus loop) - H3 + M1: Replace custom selection with built-in
rowSelection(batch events and module-level state) - AR1: Create typeahead editor factory (largest code reduction)
- M3 + AR2: Switch text columns to
valueFormatter - H4: Fix or remove Excel export
- H2: Refactor QuickActionsCell data fetching (most complex, can be deferred)
- M2–M6, L1–L7: Clean up remaining issues
Steps 1–4 are independent and can be done in parallel.
Copyright: © Arda Systems 2025-2026, All rights reserved