Data Grid

Data Grid efficiently organizes and displays large datasets, allowing users to adjust the view to their specific needs.

Component checklist

React

Is new design vision part implemented using new tokens?

Up to date

Resources

Example


                                                        
                                                        
                                                            import { DataGrid, type DataGridState } from '@mews/b2b-ui';
                                                        import { NativeCalendarService } from '@optimus-web/core';
                                                        import { useDateFormatPattern } from '@mews/framework/form';
                                                        
                                                        const initialState: Partial<DataGridState> = {
                                                            grouping: ['ledgerType'],
                                                            columnVisibility: {
                                                                code: false,
                                                                creator: false,
                                                                created: false,
                                                                consumed: false,
                                                                centerCode: false,
                                                                taxRateCode: false,
                                                                taxName: false,
                                                                service: false,
                                                                isCanceled: false,
                                                            },
                                                            columnPinning: { right: ['value'] },
                                                        };
                                                        
                                                        <DataGrid
                                                            model="client"
                                                            columns={columns}
                                                            data={data ?? []}
                                                            enableRowSelection={true}
                                                            initialState={initialState}
                                                            onStateChange={handleStateChange}
                                                            maxHeight={600}
                                                            meta={{ calendarService: NativeCalendarService, dateFormatPattern }}
                                                            onRowsChange={handleRowsChange}
                                                            headerQuickFiltersSlot={<SomeComponent />}
                                                            headerActionsSlot={<AnotherComponent />}
                                                            fillAvailableHeight={{ minHeight: 400, bottomOffset: 96 }}
                                                            loading={isLoading}
                                                        />
                                                        
                                                            

Installation

The DataGrid component is part of the @mews/b2b-ui package.


                                                        
                                                        
                                                            import {
                                                          DataGrid,
                                                          createColumnHelper,
                                                          ColumnFilterType,
                                                          // Filter Operations
                                                          StringFilterOperation,
                                                          NumberFilterOperation,
                                                          DateFilterOperation,
                                                          DateTimeFilterOperation,
                                                          MultiSelectFilterOperation,
                                                          BooleanFilterOperation,
                                                          // Types
                                                          type DataGridState,
                                                          type DataGridProps,
                                                          type ColumnDef,
                                                          type ColumnFilter,
                                                          type DataGridActionToolbarConfig,
                                                        } from '@mews/b2b-ui';
                                                        
                                                        // For DateTime filtering, also import:
                                                        import { DateTimeService } from '@mews-framework/datetime';
                                                        import { NativeCalendarService, NativeTimeService } from '@optimus-web/core';
                                                        
                                                            

Data Models

DataGrid supports two distinct data models via discriminated union types.

Client-Side Model

All data is loaded upfront and operations (sorting, filtering, grouping) are computed in the browser. Best for small to medium datasets (hundreds to a few thousand rows).


                                                        
                                                        
                                                            <DataGrid
                                                          model="client"
                                                          data={myData}
                                                          columns={columns}
                                                        />
                                                        
                                                            

Props specific to client-side:

  • model: 'client' - Discriminator
  • data: TData[] - Array of data rows

Server-Side Model

Data is fetched from the server with operations applied server-side. Best for large datasets where loading all data upfront would impact performance.


                                                        
                                                        
                                                            <DataGrid
                                                          model="server"
                                                          useData={(state) => {
                                                            // state contains sorting, filtering, pagination info
                                                            const { data, totalRowCount } = useMyServerQuery(state);
                                                            return { data, totalRowCount };
                                                          }}
                                                          columns={columns}
                                                          columnUniqueValues={{
                                                            status: ['Active', 'Inactive', 'Pending'],
                                                            category: ['Electronics', 'Clothing', 'Food'],
                                                          }}
                                                        />
                                                        
                                                            

Props specific to server-side:

  • model: 'server' - Discriminator
  • useData: (state: DataGridState) => { data, totalRowCount, isLoading?, error? } - Data fetching hook
  • columnUniqueValues?: Record<string, string[]> - Pre-computed unique values for MultiSelect filters

Props Reference

Base Props (Both Models)

Prop

Type

Default

Description

columns*

ColumnDef<TData>[]

-

Column definitions (TanStack Table format)

title

ReactNode

-

Table title displayed in header

subTitle

ReactNode

-

Subtitle below the title

defaultColumn

Partial<ColumnDef<TData>>

-

Default values applied to all columns

initialState

Partial<DataGridState>

-

Initial state (sorting, filters, grouping, etc.)

onStateChange

(state: DataGridState) => void

-

Callback fired when any state changes

onRowsChange

(rows: RowModel<TData>) => void

-

Callback fired when visible rows change

getRowId

(row, index, parent?) => string

-

Custom function to generate row IDs

getRowCanExpand

(row: Row<TData>) => boolean

-

Controls whether a row can be expanded. By default, rows with subRows can expand.

getRowIsVisible

(row: Row<TData>) => boolean

-

Controls whether a row is rendered. Hidden rows are excluded from the DOM.

Feature Toggles

Props to enable/disable DataGrid features. All default to true unless otherwise noted.

Prop

Type

Default

Description

enableFilters

boolean

true

Enable column filtering UI

enableGlobalFilter

boolean

true

Enable global search input

enableSorting

boolean

true

Enable column sorting

enableGrouping

boolean

true

Enable column grouping

enableExpanding

boolean

true

Enable row expanding (for grouped rows)

enableRowSelection

boolean

true

Enable row selection checkboxes

enableColumnPinning

boolean

true

Callback fired when visible rows change

enableColumnResizing

boolean

true

Custom function to generate row IDs

enableColumnOrdering

boolean

true

Enable column drag-and-drop reordering

enableHiding

boolean

true

Enable column visibility toggle

autoResetExpanded

boolean

false

Auto-reset expanded state when data changes

showGroupRowCount

boolean | (row) => number | null

false

Show row count in grouped headers. Pass a function to customize per group.

UI Configuration

Prop

Type

Default

Description

emptyState

EmptyStateProps

-

Configuration for empty state display

virtualized

boolean

-

Enable row virtualization for large datasets

minHeight

CSSProperties['minHeight']

-

Minimum height of the DataGrid

maxHeight

CSSProperties['maxHeight']

-

Maximum height (enables vertical scroll)

fillAvailableHeight

boolean

-

Fill available vertical space

loading

boolean

-

Show loading state

error

boolean

-

Show error state

meta

TableMeta

-

Metadata for date/time services (see Meta Configuration)

actionToolbar

DataGridActionToolbarConfig<TData>

-

Bulk action toolbar configuration

headerQuickFiltersSlot

ReactNode

-

Slot for quick filter controls in header

headerActionsSlot

ReactNode

-

Slot for 1-3 action buttons in header

DataGridState

The state object that tracks all DataGrid operations. This is passed to onStateChange and used in initialState.


                                                        
                                                        
                                                            interface DataGridState {
                                                          pagination: {
                                                            pageSize: number;      // Rows per page
                                                            pageIndex: number;     // Current page (0-indexed)
                                                          };
                                                          sorting: SortingState;   // Array<{ id: string; desc: boolean }>
                                                          columnFilters: Array<{
                                                            id: string;            // Column ID
                                                            value: ColumnFilter[]; // Array of filters for this column
                                                          }>;
                                                          globalFilter: string;    // Global search text
                                                          grouping: GroupingState; // string[] - Column IDs to group by
                                                          expanded: ExpandedState; // true | Record<string, boolean>
                                                          columnVisibility: VisibilityState;   // Record<columnId, boolean>
                                                          columnOrder: ColumnOrderState;       // string[] - Column order
                                                          columnPinning: ColumnPinningState;   // { left?: string[]; right?: string[] }
                                                          columnSizing: ColumnSizingState;     // Record<columnId, number>
                                                          rowSelection: RowSelectionState;     // Record<rowId, boolean>
                                                        }
                                                        
                                                            

Column Definitions

Use createColumnHelper for type-safe column definitions. DataGrid uses TanStack Table's column definition format.

Basic Column


                                                        
                                                        
                                                            const columnHelper = createColumnHelper<Person>();
                                                        
                                                        const columns = [
                                                          columnHelper.accessor('firstName', {
                                                            header: () => 'First Name',
                                                            cell: ({ getValue }) => getValue(),
                                                          }),
                                                        ];
                                                        
                                                            

Column with Filter Type


                                                        
                                                        
                                                            columnHelper.accessor('status', {
                                                          header: () => 'Status',
                                                          meta: {
                                                            filterType: ColumnFilterType.MultiSelect,
                                                          },
                                                        }),
                                                        
                                                            

Column with Custom Cell Renderer


                                                        
                                                        
                                                            columnHelper.accessor('price', {
                                                          header: () => 'Price',
                                                          cell: ({ getValue }) => `$${getValue().toFixed(2)}`,
                                                          meta: {
                                                            filterType: ColumnFilterType.Number,
                                                            align: 'end',
                                                            width: 120,
                                                          },
                                                        }),
                                                        
                                                            

Display Column (No Data Accessor)


                                                        
                                                        
                                                            columnHelper.display({
                                                          id: 'actions',
                                                          header: 'Actions',
                                                          cell: ({ row }) => <ActionsMenu row={row.original} />,
                                                          enableSorting: false,
                                                          enableHiding: false,
                                                          enablePinning: false,
                                                          enableResizing: false,
                                                        }),
                                                        
                                                            

Column with Grouping and Aggregation


                                                        
                                                        
                                                            columnHelper.accessor('amount', {
                                                          header: () => 'Amount',
                                                          aggregationFn: 'sum',  // Built-in: sum, min, max, mean, count, median, unique, uniqueCount, extent
                                                          aggregatedCell: ({ getValue }) => <strong>${getValue().toFixed(2)}</strong>,
                                                          enableGrouping: false, // Prevent grouping by this column
                                                          footer: ({ table }) => {
                                                            // Custom footer calculation
                                                            const total = table.getRowModel().rows.reduce(
                                                              (sum, row) => sum + (row.getValue('amount') as number),
                                                              0
                                                            );
                                                            return <strong>Total: ${total.toFixed(2)}</strong>;
                                                          },
                                                        }),
                                                        
                                                            

Column with Array Values (MultiSelect Filter)

For columns containing arrays, you must provide getUniqueValues to flatten values for the filter dropdown:


                                                        
                                                        
                                                            columnHelper.accessor('tags', {
                                                          header: () => 'Tags',
                                                          cell: ({ getValue }) => getValue().join(', '),
                                                          meta: { filterType: ColumnFilterType.MultiSelect },
                                                          // REQUIRED for array values - extracts individual values for filter options
                                                          getUniqueValues: (row) => row.tags,
                                                        }),
                                                        
                                                            

Column with Custom Filter Sorting

Use filterSortFn to customize how MultiSelect filter options are sorted:


                                                        
                                                        
                                                            const PRIORITY_ORDER = ['Critical', 'High', 'Medium', 'Low'];
                                                        
                                                        columnHelper.accessor('priority', {
                                                          header: () => 'Priority',
                                                          meta: {
                                                            filterType: ColumnFilterType.MultiSelect,
                                                            // Custom sort: priority order first, then alphabetical
                                                            filterSortFn: (a, b) => {
                                                              const aIndex = PRIORITY_ORDER.indexOf(a);
                                                              const bIndex = PRIORITY_ORDER.indexOf(b);
                                                              if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
                                                              if (aIndex !== -1) return -1;
                                                              if (bIndex !== -1) return 1;
                                                              return a.localeCompare(b);
                                                            },
                                                          },
                                                        }),
                                                        
                                                            

Column Meta Options

Option

Type

Description

filterType

ColumnFilterType

Filter type: String, Number, Date, DateTime, MultiSelect, Boolean

filterSortFn

(a: string, b: string) => number

Custom sort function for MultiSelect filter options

globalFilterValue

(value: unknown) => string

Transform cell value for global search matching

width

number

Fixed column width in pixels

align

start | end

Both header and cell alignment

headerAlign

start | end

Header alignment (overrides align)

cellAlign

start | end

Cell alignment (overrides align)

Column-Level Feature Control

Option

Type

Description

enableSorting

boolean

Enable/disable sorting for this column

enableHiding

boolean

Enable/disable hiding for this column

enablePinning

boolean

Enable/disable pinning for this column

enableResizing

boolean

Enable/disable resizing for this column

enableGrouping

boolean

Enable/disable grouping by this column

Filter Types

DataGrid supports six filter types, each with specific operations. Filters now include a type discriminator field.

String Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.String }
                                                        
                                                            

Operations (StringFilterOperation):

  • Contains - Value contains the search text
  • Equals - Exact match
  • StartsWith - Value starts with text
  • EndsWith - Value ends with text

Number Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.Number }
                                                        
                                                            

Operations (NumberFilterOperation):

  • Equals - Exact number match
  • GreaterThan - Value > number
  • LessThan - Value < number
  • GreaterThanOrEqual - Value >= number
  • LessThanOrEqual - Value <= number
  • Between - Value between two numbers (inclusive) NOT IMPLEMENTED

Date Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.Date }
                                                        
                                                            

Operations (DateFilterOperation):

  • Equals - Exact date match
  • After - Date is after
  • Before - Date is before
  • Between - Date is between two dates NOT IMPLEMENTED

Requirements: Requires meta.calendarService on the DataGrid.

DateTime Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.DateTime }
                                                        
                                                            

Operations (DateTimeFilterOperation):

  • Equals - Exact datetime match
  • After - Datetime is after
  • Before - Datetime is before
  • Between - Datetime is between two dates NOT IMPLEMENTED

Requirements:

Requires all of the following in DataGrid meta:

  • calendarService - For date picker
  • timeService - For time picker
  • dateTimeService (mews/framework)- For datetime parsing/formatting
  • timezone (mews/framework)- Timezone string (e.g., 'Europe/Prague')

MultiSelect Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.MultiSelect }
                                                        
                                                            

Operations (MultiSelectFilterOperation):

  • In - Value is one of selected options
  • NotIn - Value is not one of selected options

Note: For columns containing arrays (e.g., tags), you must provide getUniqueValues on the column definition to flatten values for the filter dropdown:


                                                        
                                                        
                                                            getUniqueValues: (row) => row.tags
                                                        
                                                            

Boolean Filter


                                                        
                                                        
                                                            meta: { filterType: ColumnFilterType.Boolean }
                                                        
                                                            

Operations (BooleanFilterOperation):

  • In - Value matches selected boolean

Meta Configuration

The meta prop provides services required for date/time filtering and formatting.


                                                        
                                                        
                                                            import { NativeCalendarService, NativeTimeService } from '@optimus-web/core';
                                                        import { DateTimeService } from '@mews-framework/datetime';
                                                        
                                                        <DataGrid
                                                          // ...other props
                                                          meta={{
                                                            // Required for Date filter
                                                            calendarService: NativeCalendarService,
                                                        
                                                            // Required for DateTime filter
                                                            timeService: NativeTimeService,
                                                            dateTimeService: DateTimeService,
                                                            timezone: 'Europe/Prague',
                                                        
                                                            // Optional: custom date format pattern
                                                            dateFormatPattern: 'MM/dd/yyyy',
                                                          }}
                                                        />
                                                        
                                                            

Meta Properties

Property

Type

Required For

Description

calendarService

CalendarService Date

DateTime filters

Service for date operations

timeService

TimeService

DateTime filter

Service for time operations

dateTimeService

typeof DateTimeService

DateTime filter

Service from @mews-framework/datetime

timezone

string

DateTime filter

IANA timezone (e.g., 'Europe/Prague', 'America/New_York')

dateFormatPattern

string

Optional

Date format pattern (e.g., 'MM/dd/yyyy')

Initial State Examples

Sorting


                                                        
                                                        
                                                            initialState={{
                                                          sorting: [{ id: 'createdAt', desc: true }],
                                                        }}
                                                        
                                                            

Column Filters


                                                        
                                                        
                                                            initialState={{
                                                          columnFilters: [
                                                            {
                                                              id: 'status',
                                                              value: [{ type: 'multiSelect', operation: 'in', value: ['active', 'pending'] }]
                                                            },
                                                            {
                                                              id: 'amount',
                                                              value: [{ type: 'number', operation: 'greaterThan', value: 100 }]
                                                            },
                                                          ],
                                                        }}
                                                        
                                                            

Grouping


                                                        
                                                        
                                                            initialState={{
                                                          grouping: ['category', 'subcategory'],
                                                          expanded: true, // Expand all groups by default
                                                          // or expand specific rows:
                                                          // expanded: { 'category:Electronics': true, 'category:Clothing': false },
                                                        }}
                                                        
                                                            

Column Pinning


                                                        
                                                        
                                                            initialState={{
                                                          columnPinning: {
                                                            left: ['name'],      // Pin to left
                                                            right: ['actions'],  // Pin to right
                                                          },
                                                        }}
                                                        
                                                            

Column Visibility


                                                        
                                                        
                                                            initialState={{
                                                          columnVisibility: {
                                                            internalId: false,   // Hidden by default
                                                            debugInfo: false,
                                                          },
                                                        }}
                                                        
                                                            

Column Order


                                                        
                                                        
                                                            initialState={{
                                                          columnOrder: ['name', 'status', 'amount', 'createdAt', 'actions'],
                                                        }}
                                                        
                                                            

Pagination


                                                        
                                                        
                                                            initialState={{
                                                          pagination: {
                                                            pageIndex: 0,
                                                            pageSize: 25,
                                                          },
                                                        }}
                                                        
                                                            

Row Visibility and Expansion

DataGrid provides two callbacks for controlling row behavior: getRowCanExpand and getRowIsVisible.

getRowCanExpand

Controls whether a row's expand/collapse toggle is shown. By default, any row with subRows can be expanded.


                                                        
                                                        
                                                            // Only allow expansion for groups with more than 2 items
                                                        <DataGrid
                                                            getRowCanExpand={(row) => (row.subRows?.length ?? 0) > 2}
                                                            // ...other props
                                                        />
                                                        
                                                        
                                                            

Use cases:

  • Prevent expansion of small groups
  • Conditionally disable expansion based on row data
  • Implement "lazy loading" patterns where some groups aren't expandable until data is fetched

getRowIsVisible

Controls whether a row is rendered in the DOM. Hidden rows are completely excluded—they don't appear in the UI and don't participate in keyboard navigation.


                                                        
                                                        
                                                            // Only show rows where status is enabled
                                                        <DataGrid
                                                            getRowIsVisible={(row) => row.original.status === true}
                                                            // ...other props
                                                        />
                                                        
                                                            

Use cases:

  • Implement custom filtering logic beyond column filters
  • Hide rows based on complex business rules
  • Create "soft delete" patterns where deleted items are hidden but not removed from data

Aligning getRowIsVisible with enableRowSelection

Important: When using getRowIsVisible with row selection, ensure hidden rows cannot be selected. Otherwise, users may unknowingly have hidden rows in their selection.


                                                        
                                                        
                                                            // ❌ Bad: Hidden rows can still be selected
                                                        <DataGrid
                                                            getRowIsVisible={(row) => row.original.status === true}
                                                            enableRowSelection={true}
                                                        />
                                                        
                                                        // ✅ Good: Selection logic matches visibility logic
                                                        const isRowEnabled = (row) => row.original.status === true;
                                                        
                                                        <DataGrid
                                                            getRowIsVisible={isRowEnabled}
                                                            enableRowSelection={isRowEnabled}
                                                        />
                                                        
                                                            

This ensures that:

  • Users only see selectable rows
  • Select all only selects visible rows
  • Selection state remains consistent with what users can interact with

Action Toolbar

Configure bulk actions for selected rows using the floating action toolbar.


                                                        
                                                        
                                                            import { ActionToolbarButton } from '@optimus-web/core';
                                                        import { IconName } from '@optimus-web/icons';
                                                        
                                                        <DataGrid
                                                          enableRowSelection
                                                          actionToolbar={{
                                                            actions: (table) => (
                                                              <>
                                                                <ActionToolbarButton
                                                                  icon={IconName.Edit}
                                                                  onClick={() => {
                                                                    const selectedRows = table.getSelectedRows().rows;
                                                                    handleEdit(selectedRows.map(row => row.original));
                                                                  }}
                                                                  type="button"
                                                                  accessibleText="Edit selected"
                                                                >
                                                                  Edit
                                                                </ActionToolbarButton>
                                                                <ActionToolbarButton
                                                                  icon={IconName.Delete}
                                                                  onClick={() => {
                                                                    const selectedRows = table.getSelectedRows().rows;
                                                                    handleDelete(selectedRows.map(row => row.original));
                                                                  }}
                                                                  type="button"
                                                                  accessibleText="Delete selected"
                                                                >
                                                                  Delete
                                                                </ActionToolbarButton>
                                                              </>
                                                            ),
                                                          }}
                                                        />
                                                        
                                                            

DataGridActionToolbarTable Interface

The table parameter provides read-only access to selection state:

Method

Return Type

Description

getSelectedRows()

RowModel<TData>

Get all selected rows (filtered, leaf rows only)

getVisibleRows()

RowModel<TData>

Get all visible (filtered) rows

isSomeSelected()

boolean

Check if some (but not all) rows are selected

isAllSelected()

boolean

Check if all visible rows are selected

Responsive Action Toolbar


                                                        
                                                        
                                                            import { isAtLeastTablet, useBreakpointRange } from '@optimus-web/breakpoints';
                                                        
                                                        const ActionToolbarActions = ({ table }) => {
                                                          const breakpointRange = useBreakpointRange();
                                                          const isCompact = !isAtLeastTablet(breakpointRange);
                                                        
                                                          return (
                                                            <ActionToolbarButton
                                                              icon={IconName.Edit}
                                                              onClick={() => handleEdit(table.getSelectedRows().rows)}
                                                              type="button"
                                                              accessibleText="Edit"
                                                            >
                                                              {!isCompact && 'Edit'} {/* Hide label on mobile */}
                                                            </ActionToolbarButton>
                                                          );
                                                        };
                                                        
                                                            

URL State Synchronization

Persist DataGrid state in URL for shareable links using the useDataGridUrlState hook.


                                                        
                                                        
                                                            import { useDataGridUrlState } from '@mews-commander/utils';
                                                        
                                                        const MyComponent = () => {
                                                          const [initialState, handleStateChange] = useDataGridUrlState('myDataGrid', {
                                                            defaultState: {
                                                              sorting: [{ id: 'createdAt', desc: true }],
                                                              grouping: ['category'],
                                                              columnPinning: { right: ['actions'] },
                                                            },
                                                          });
                                                        
                                                          return (
                                                            <DataGrid
                                                              model="client"
                                                              data={data}
                                                              columns={columns}
                                                              initialState={initialState}
                                                              onStateChange={handleStateChange}
                                                            />
                                                          );
                                                        };
                                                        
                                                            

Synced State Keys

The following state keys are synchronized to the URL:

  • sorting
  • columnFilters
  • globalFilter
  • grouping
  • columnVisibility
  • columnOrder
  • columnPinning

Note: pagination, expanded, columnSizing, and rowSelection are NOT synced to URL.

Persisted Views (URL + localStorage)

For grids that need user-saveable views on top of URL state synchronization, use useDataGridViews. It wraps useDataGridUrlState and adds CRUD over named views, an "active view" pointer, and an hasUnsavedChanges flag. URL stays the source of truth for grid state; views are snapshots of that state, persisted to localStorage by default in this V1.


                                                        
                                                        
                                                            import { useRef } from 'react';
                                                        import { DataGrid, DataGridHandle, DataGridViewsConfig } from '@mews/b2b-ui';
                                                        import { useDataGridViews } from '@mews-commander/utils';
                                                        
                                                        const DEFAULT_STATE = {
                                                          sorting: [{ id: 'isoDate', desc: false }],
                                                          grouping: ['isoDate'],
                                                          columnPinning: { right: ['price', 'actions'] },
                                                        };
                                                        
                                                        const MyListView = () => {
                                                          const dataGridRef = useRef<DataGridHandle>(null);
                                                        
                                                          const {
                                                            dataGridState,
                                                            handleStateChange,
                                                            views,
                                                            activeViewId,
                                                            hasUnsavedChanges,
                                                            saveView,
                                                            updateView,
                                                            deleteView,
                                                            applyView,
                                                            duplicateView,
                                                          } = useDataGridViews('myGrid', { dataGridRef });
                                                        
                                                          const viewsConfig: DataGridViewsConfig = {
                                                            items: views,
                                                            activeViewId,
                                                            hasUnsavedChanges,
                                                            onApply: applyView,
                                                            onSaveNew: saveView,
                                                            onSaveChanges: () => {
                                                              if (activeViewId) {
                                                                // Merge defaults so the saved snapshot includes baseline state
                                                                updateView(activeViewId, { state: { ...DEFAULT_STATE, ...dataGridState } });
                                                              }
                                                            },
                                                            onDelete: deleteView,
                                                            onRename: (viewId, newName) => updateView(viewId, { name: newName }),
                                                            onDuplicate: duplicateView,
                                                          };
                                                        
                                                          return (
                                                            <DataGrid
                                                              ref={dataGridRef}
                                                              model="client"
                                                              data={data}
                                                              columns={columns}
                                                              initialState={{ ...DEFAULT_STATE, ...dataGridState }}
                                                              onStateChange={handleStateChange}
                                                              views={viewsConfig}
                                                            />
                                                          );
                                                        };
                                                        
                                                            

How It Works

  • URL is the source of truth. Grid state lives in the URL search param keyed by gridId (the same JSON-string contract as useDataGridUrlState).
  • Views are snapshots. Each view stores a Partial<SyncableDataGridState> — sorting, filters, grouping, visibility, order, pinning. Pagination, expansion, column sizing, and row selection are never persisted.
  • Active view tracks intent. activeViewId is the view the user is "working from". hasUnsavedChanges is true when current URL state differs from that view's saved state (compared on syncable keys only).
  • Imperative apply via ref. applyView and duplicateView call dataGridRef.current?.setState(...), which resets to defaults and applies the view; the resulting onStateChange writes back to the URL.

Mount-Time Sync

On first render, useDataGridViews reconciles URL ↔ stored active view:

URL state on mount

Behavior

Empty

Apply stored activeViewId, or defaultActiveViewId`if provided.

Matches a saved view

Mark that view active.

Custom (no match)

Keep URL state; restore stored activeViewId as "active with unsaved changes".

This sync runs once at mount. After that, all changes are user-driven (apply, save, delete, etc.).

API

useDataGridViews(gridId, options)

Argument

Type

Description

gridId

string

Required. Used as both the URL search-param key and the localStorage namespace (dataGrid.views.${gridId}, dataGrid.activeView.${gridId}). Must be unique per grid.

options.dataGridRef

RefObject<DataGridHandle | null>

Required. Used to imperatively apply view state to the mounted grid.

options.defaultActiveViewId

string | null

Optional. Auto-applied on mount if URL is empty and no activeViewId is stored. Default: null

options.storage

ViewsStorage

Optional. Custom storage adapter (e.g. backend-backed). Default: localStorageAdapter.

options.enabled

boolean

Optional. When false, disables view CRUD and sync entirely (URL state still works). Use to gate behind a feature flag. Must be stable from first render — toggling false → true after mount won't trigger hydration. Default: true.

Returned values

Name

Type

Description

dataGridState

Partial<DataGridState>

Live state synced with URL. Pass to `<DataGrid initialState>` (merge with your defaults).

handleStateChange

(state: Partial<DataGridState>) => void

Pass to `<DataGrid onStateChange>`. Writes syncable keys to the URL.

views

DataGridView[]

All saved views for this `gridId`.

activeViewId

string | null

ID of the view the user is currently working from, or `null`.

hasUnsavedChanges

boolean

true when current URL state differs from the active view's stored state.

saveView

(name: string) => void

Snapshot current state as a new view and mark it active. Wire to onSaveNew.

updateView

(id: string, updates: Omit<Partial<DataGridView>, 'id'>) => void

Patch a view's name and/or state. No-op for readOnly views. Use for both "save changes" and "rename".

deleteView

(id: string) => void

Remove a view. No-op for readOnly views. Clears active view if it was the deleted one.

applyView

(id: string) => void

Apply a view's state to the grid and mark it active. Wire to onApply.

duplicateView

(id: string) => void

Clone a view with a localized (copy) / (copy N) suffix, mark it active, and apply it. Wire to onDuplicate.

Wiring to DataGridViewsConfig

The hook return shape does not map 1-to-1 to DataGridViewsConfig. Wire it like this:

DataGridViewsConfig prop

Source

items

views

activeViewId

activeViewId

hasUnsavedChanges

hasUnsavedChanges

onApply

applyView

onSaveNew

saveView

onSaveChanges

() => updateView(activeViewId, { state: { ...DEFAULTS, ...dataGridState } })

onDelete

deleteView

onRename

(id, name) => updateView(id, { name })

onDuplicate

duplicateView

onCopyLink

Optional. Defaults to copying window.location.href.

Note: onSaveChanges should merge your default state into the snapshot so the saved view contains the full baseline (defaults + user changes), not just the user diff.

Read-Only Views

Set readOnly: true on a DataGridView to lock it. updateView and deleteView become no-ops for that view. Useful for system-provided defaults you ship with the app.

Custom Storage

Replace localStorage with any persistence layer (e.g. backend API) by passing a custom adapter:


                                                        
                                                        
                                                            import type { ViewsStorage } from '@mews-commander/utils';
                                                        
                                                        const backendStorage: ViewsStorage = {
                                                          getViews: (gridId) => /* … */,
                                                          saveView: (gridId, view) => /* … */,
                                                          updateView: (gridId, id, updates) => /* … */,
                                                          deleteView: (gridId, id) => /* … */,
                                                          getActiveViewId: (gridId) => /* … */,
                                                          setActiveViewId: (gridId, id) => /* … */,
                                                        };
                                                        
                                                        useDataGridViews('myGrid', { dataGridRef, storage: backendStorage });
                                                        
                                                            

The default localStorageAdapter validates stored payloads with Zod and self-heals corrupted entries by clearing them.

Synced State Keys

Views (and the URL) only persist:

  • sorting
  • columnFilters
  • globalFilter
  • grouping
  • columnVisibility
  • columnOrder
  • columnPinning

Note: pagination, expanded, columnSizing, and rowSelection are intentionally excluded — they are session-specific or too volatile to persist.


                                                        
                                                        
                                                            columnHelper.accessor('amount', {
                                                          header: () => 'Amount',
                                                          cell: ({ getValue }) => `$${getValue().toFixed(2)}`,
                                                          footer: ({ table }) => {
                                                            const rows = table.getRowModel().rows;
                                                            const total = rows.reduce((sum, row) => {
                                                              const amount = row.getValue('amount');
                                                              return sum + (typeof amount === 'number' ? amount : 0);
                                                            }, 0);
                                                        
                                                            return (
                                                              <Stack vertical spacing="25" itemAlignment="end">
                                                                <Typography textStyle="bodyMediumStrong">Total</Typography>
                                                                <Typography textStyle="bodyLargeStrong">${total.toFixed(2)}</Typography>
                                                              </Stack>
                                                            );
                                                          },
                                                          meta: { filterType: ColumnFilterType.Number, align: 'end' },
                                                        }),
                                                        
                                                            

Aggregation in Grouped Rows


                                                        
                                                        
                                                            columnHelper.accessor('revenue', {
                                                          header: () => 'Revenue',
                                                          // Built-in aggregation functions: sum, min, max, mean, median, unique, uniqueCount, count, extent
                                                          aggregationFn: 'sum',
                                                          // Custom rendering for aggregated (grouped) cells
                                                          aggregatedCell: ({ getValue }) => (
                                                            <Typography textStyle="bodyMediumStrong">
                                                              ${getValue<number>().toFixed(2)}
                                                            </Typography>
                                                          ),
                                                          cell: ({ getValue }) => `$${getValue().toFixed(2)}`,
                                                          meta: { align: 'end' },
                                                        }),
                                                        
                                                            

Custom Aggregation Function


                                                        
                                                        
                                                            columnHelper.accessor('scores', {
                                                          header: () => 'Avg Score',
                                                          aggregationFn: (columnId, leafRows, childRows) => {
                                                            const values = leafRows.map(row => row.getValue(columnId) as number);
                                                            const sum = values.reduce((a, b) => a + b, 0);
                                                            return values.length > 0 ? sum / values.length : 0;
                                                          },
                                                          aggregatedCell: ({ getValue }) => getValue<number>().toFixed(1),
                                                        }),
                                                        
                                                            

Migration from Table

DataGrid replaces the deprecated Table component. Key differences:

Aspect

Table (Deprecated)

DataGrid

Data model

Server-side only

Client + Server

State management

useTableState hook

DataGridState + onStateChange

Filters

External implementation

Built-in with 6 filter types

Grouping

Not supported

Built-in with aggregation

Column pinning

fixedFirstColumn prop

enableColumnPinning + initialState.columnPinning

Global search

External via TableHeader

Built-in enableGlobalFilter

Row selection

useRowSelection hook

Built-in enableRowSelection

Bulk actions

TableBulkActions component

Built-in actionToolbar prop

Performance Tips

  1. Use virtualization for datasets > 100 rows
  2. Set explicit column widths to avoid layout recalculations
  3. Memoize columns to prevent unnecessary re-renders
  4. Use server-side model for very large datasets (> 10,000 rows)
  5. Limit initial visible columns - users can show/hide as needed
  6. Avoid complex cell renderers - keep cell components lightweight
  7. Use getRowId for stable row identity

Virtualization Tuning

DataGrid virtualizes by default. Three props let you optimize for your use case:


                                                        
                                                        
                                                            // Small datasets: skip virtualization overhead for <50 rows
                                                        <DataGrid virtualizationThreshold={50} />
                                                        
                                                        // Consistent tall rows (e.g., 80px)
                                                        <DataGrid estimatedRowHeight={80} />
                                                        
                                                        // Variable height rows (multi-line content)
                                                        <DataGrid enableDynamicRowHeight />
                                                        
                                                            

When to use each:

  • virtualizationThreshold → Small tables where DOM rendering is faster than virtualization setup
  • estimatedRowHeight → Rows are uniform but taller/shorter than default (56px)
  • enableDynamicRowHeight → Rows genuinely vary in height (last resort, has perf cost)