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 `_