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
-----------------
1. `Overview`_
2. `Features`_
3. `Quick Start`_
4. `Implementation Examples`_
5. `Logout Integration`_
6. `Advanced Usage`_
7. `Storage Format`_
8. `Performance Notes`_
9. `Troubleshooting`_
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
~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
import { useAgGridState } from '@/hooks/agGrid';
import { useCurrentUser } from '@/auth/hooks/useCurrentUser';
Step 2: Use in Component
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
const currentUser = useCurrentUser();
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
enabled: !!currentUser.userId,
});
Step 3: Pass to AG Grid
~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
Implementation Examples
-----------------------
Client-Side Grid Example
~~~~~~~~~~~~~~~~~~~~~~~~
Complete example for a client-side grid:
.. code-block:: typescript
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 (
);
};
Server-Side Grid Example
~~~~~~~~~~~~~~~~~~~~~~~~~
Example for a server-side grid with datasource:
.. code-block:: typescript
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(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 (
);
};
Logout Integration
------------------
Clear all saved grid states when a user logs out to prevent state conflicts:
.. code-block:: typescript
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
enabled: !!currentUser.userId && !isGuestMode, // Disable for guests
});
Custom Debounce Time
~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
const { initialState, onGridReady } = useAgGridState({
gridId: 'work-orders',
userId: currentUser.userId || '',
debounceMs: 1000, // Wait 1 second before saving
});
Manual State Clearing
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
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
~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: typescript
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:
.. code-block:: text
ems-grid-{gridId}-{userId}
Example storage keys:
- ``ems-grid-work-orders-user123``
- ``ems-grid-assets-user123``
- ``ems-grid-locations-user456``
State Contents
~~~~~~~~~~~~~~
The saved state includes the following (per AG Grid's GridState interface):
- ``version`` - AG Grid version for migration
- ``aggregation`` - Aggregation functions
- ``columnGroup`` - Opened column groups
- ``columnOrder`` - Column order
- ``columnPinning`` - Pinned columns (left/right)
- ``columnSizing`` - Column widths/flex
- ``columnVisibility`` - Hidden columns
- ``filter`` - Column filters and advanced filter
- ``focusedCell`` - Currently focused cell (client-side only)
- ``pagination`` - Current page
- ``pivot`` - Pivot mode and columns
- ``cellSelection`` - Selected cell ranges
- ``rowGroup`` - Row group columns
- ``rowGroupExpansion`` - Expanded row groups
- ``rowSelection`` - Selected rows (client-side only)
- ``sideBar`` - Side bar state
- ``sort`` - 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
~~~~~~~~~~~~~~~~~~~~
1. **Check if user ID is available:**
.. code-block:: typescript
console.log('User ID:', currentUser.userId);
2. **Verify localStorage is enabled:**
.. code-block:: typescript
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
console.log('localStorage is available');
} catch (e) {
console.error('localStorage is blocked:', e);
}
3. **Check browser console for errors** - Look for ``[AG Grid State]`` prefixed messages
State Not Restoring
~~~~~~~~~~~~~~~~~~~
1. Verify ``initialState`` is passed to ``AgGridReact`` component
2. Ensure ``onGridReady`` callback is properly connected
3. Check if state exists in localStorage:
.. code-block:: typescript
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.
Related Resources
-----------------
For more information on AG Grid state management, refer to the official documentation:
- `AG Grid State API `_
- `AG Grid Events `_
- `Client-Side Row Model `_
- `Server-Side Row Model `_