AG Grid State Persistence
This document describes the localStorage-based persistence system for AG Grid state across browser sessions and page navigations.
Table of Contents
Overview
The AG Grid state persistence module provides automatic saving and restoration of user grid customizations using localStorage. Each user maintains their own grid state, which persists across browser sessions.
Features
The persistence system supports the following AG Grid features:
Column State: Order, width, visibility, pinning
Filters: Column filters and advanced filters
Sorting: Multi-column sort state
Pagination: Current page and page size
Row Groups: Group columns and expansion state
Cell Selection: Selected cell ranges
User-Specific: Separate state per user
Auto-Save: Debounced saves on state changes (default 500ms)
Row Model Support: Works with both client-side and server-side row models
Quick Start
Step 1: Import the Hook
import { useAgGridState } from '@/hooks/agGrid';
import { useCurrentUser } from '@/auth/hooks/useCurrentUser';
Step 2: Use in Component
const currentUser = useCurrentUser();
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
enabled: !!currentUser.userId,
});
Step 3: Pass to AG Grid
<AgGridReact
initialState={initialState}
onGridReady={onGridReady}
// ... other props
/>
Implementation Examples
Client-Side Grid Example
Complete example for a client-side grid:
import { useCurrentUser } from '@/auth/hooks/useCurrentUser';
import { useAgGridState } from '@/hooks/agGrid';
import { AgGridReact } from 'ag-grid-react';
const WorkOrdersGrid = ({ workOrders, onWorkOrderClick }) => {
const currentUser = useCurrentUser();
// Add state persistence
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
enabled: !!currentUser.userId,
});
const gridOptions = useMemo(() => ({
columnDefs: [
{ field: 'id', headerName: 'ID' },
{ field: 'name', headerName: 'Name' },
{ field: 'status', headerName: 'Status' },
],
defaultColDef: {
resizable: true,
sortable: true,
filter: true,
},
pagination: true,
paginationPageSize: 20,
rowData: workOrders || [],
}), [workOrders]);
return (
<AgGridReact
gridOptions={gridOptions}
initialState={initialState}
onGridReady={onGridReady}
/>
);
};
Server-Side Grid Example
Example for a server-side grid with datasource:
import { useCurrentUser } from '@/auth/hooks/useCurrentUser';
import { useAgGridState } from '@/hooks/agGrid';
import { IServerSideDatasource, GridReadyEvent } from 'ag-grid-community';
const AssetGrid = ({ locationId }) => {
const currentUser = useCurrentUser();
const gridRef = useRef<AgGridReact>(null);
// Add state persistence
const { initialState, onGridReady: onGridReadyState } = useAgGridState({
gridId: 'assets',
userId: currentUser.userId || '',
enabled: !!currentUser.userId,
});
// Combine grid ready handlers
const handleGridReady = useCallback((params: GridReadyEvent) => {
// Call state persistence handler first
onGridReadyState(params);
// Then set up server-side datasource
const datasource: IServerSideDatasource = {
getRows: async (serverParams) => {
try {
const response = await fetch('/api/assets', {
method: 'POST',
body: JSON.stringify(serverParams.request),
});
const data = await response.json();
serverParams.success({
rowData: data.rows,
rowCount: data.total,
});
} catch (error) {
serverParams.fail();
}
},
};
params.api.setGridOption('serverSideDatasource', datasource);
}, [onGridReadyState]);
const gridOptions = useMemo(() => ({
columnDefs: [
{ field: 'name', headerName: 'Asset Name' },
{ field: 'partNumber', headerName: 'Part Number' },
],
rowModelType: 'serverSide',
serverSideStoreType: 'partial',
}), []);
return (
<AgGridReact
ref={gridRef}
gridOptions={gridOptions}
initialState={initialState}
onGridReady={handleGridReady}
/>
);
};
Logout Integration
Clear all saved grid states when a user logs out to prevent state conflicts:
import { clearAllGridStates } from '@/hooks/agGrid';
import { useCurrentUser } from '@/auth/hooks/useCurrentUser';
const handleLogout = async () => {
const currentUser = useCurrentUser();
if (currentUser.userId) {
clearAllGridStates(currentUser.userId);
}
// Continue with logout logic
await msalInstance.logoutRedirect();
};
Advanced Usage
Disable Persistence Conditionally
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
enabled: !!currentUser.userId && !isGuestMode, // Disable for guests
});
Custom Debounce Time
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
debounceMs: 1000, // Wait 1 second before saving
});
Manual State Clearing
const { initialState, onGridReady, clearState } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
});
const handleResetGrid = () => {
clearState();
window.location.reload(); // Reload to apply cleared state
};
Debug: Check Saved Grids
import { getSavedGridIds } from '@/hooks/agGrid';
const savedGrids = getSavedGridIds(currentUser.userId);
console.log('Grids with saved state:', savedGrids);
// Output: ['work-orders', 'assets', 'locations']
Storage Format
State is stored in localStorage using the following key format:
ems-grid-{gridId}-{userId}
Example storage keys:
ems-grid-work-orders-user123ems-grid-assets-user123ems-grid-locations-user456
State Contents
The saved state includes the following (per AG Grid’s GridState interface):
version- AG Grid version for migrationaggregation- Aggregation functionscolumnGroup- Opened column groupscolumnOrder- Column ordercolumnPinning- Pinned columns (left/right)columnSizing- Column widths/flexcolumnVisibility- Hidden columnsfilter- Column filters and advanced filterfocusedCell- Currently focused cell (client-side only)pagination- Current pagepivot- Pivot mode and columnscellSelection- Selected cell rangesrowGroup- Row group columnsrowGroupExpansion- Expanded row groupsrowSelection- Selected rows (client-side only)sideBar- Side bar statesort- Sort state
Performance Notes
Optimization Strategies
Debouncing: State saves are debounced (default 500ms) to prevent excessive localStorage writes
Async Operations: All localStorage operations are wrapped in try-catch blocks for error handling
Memory Usage: State is stored as JSON strings, typically less than 10KB per grid
No Impact: Does not affect grid rendering or data fetching performance
Browser Compatibility
Works in all modern browsers that support:
localStorage API
ES6+ JavaScript
Troubleshooting
State Not Persisting
Check if user ID is available:
console.log('User ID:', currentUser.userId);
Verify localStorage is enabled:
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
console.log('localStorage is available');
} catch (e) {
console.error('localStorage is blocked:', e);
}
Check browser console for errors - Look for
[AG Grid State]prefixed messages
State Not Restoring
Verify
initialStateis passed toAgGridReactcomponentEnsure
onGridReadycallback is properly connectedCheck if state exists in localStorage:
const key = `ems-grid-work-orders-${currentUser.userId}`;
console.log('Saved state:', localStorage.getItem(key));
Migration Notes
AG Grid automatically migrates older state formats when the grid version changes. No manual migration is required.