React Data Grid

ApexGrid is a web component, and react-apex-grid is the official React wrapper for it. It gives you declarative on<Event> props, TypeScript types for props and events, and a ref typed to the grid element, all generated from the element's Custom Elements Manifest.

Installation

npm install react-apex-grid apex-grid

react, react-dom and apex-grid are peer dependencies. The wrapper is tier-agnostic and holds no license gate: Enterprise features light up on their own when apex-grid-enterprise is loaded alongside.

The grid needs a bounded height, because the row virtualizer collapses without one. Pass it through style or className like any React prop.

Typed usage

createApexGrid<T>() returns a component typed to your row shape: data is T[], columns is ColumnConfiguration<T>[], and every event detail is generic over T. Call it once per data type at module scope.

import { createApexGrid } from 'react-apex-grid'
import type { ColumnConfiguration } from 'apex-grid'

interface User {
  id: number
  name: string
  email: string
}

const UserGrid = createApexGrid<User>()

export default function App() {
  const data: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob',   email: 'bob@example.com' },
  ]

  const columns: ColumnConfiguration<User>[] = [
    { key: 'id',    headerText: 'ID',    type: 'number' },
    { key: 'name',  headerText: 'Name',  sort: true, filter: true },
    { key: 'email', headerText: 'Email', sort: true },
  ]

  return (
    <UserGrid
      data={data}
      columns={columns}
      style={{ height: 480 }}
      onRowSelected={(e) => console.log('selected:', e.detail)}
      onSorted={(e) => console.log(e.detail)}
    />
  )
}

Column, event and configuration types come from apex-grid itself:

import type { ColumnConfiguration, ApexRowSelectedEvent } from 'apex-grid'

The base components

ApexGrid, ApexGridToolbar and ApexGridPaginator are the loosely typed wrappers, with data and columns typed as object. They suit JavaScript and quick usage.

import { ApexGrid } from 'react-apex-grid'

<ApexGrid data={rows} columns={columns} style={{ height: 480 }} />

Imperative access

Use a ref to call grid methods directly. The ref is typed to the grid element.

import { useRef, useEffect } from 'react'
import { createApexGrid, type ApexGridElement } from 'react-apex-grid'

const UserGrid = createApexGrid<User>()

export default function App() {
  const gridRef = useRef<ApexGridElement<User>>(null)

  useEffect(() => {
    gridRef.current?.sort({ key: 'name', direction: 'ascending' })
  }, [])

  return <UserGrid ref={gridRef} data={data} columns={columns} style={{ height: 480 }} />
}

'descending' and 'ascending' are the direction values; 'desc' and 'asc' are not accepted.

Next.js and server-side rendering

The wrapper is a client component, and the package's entry points are marked 'use client'. The grid renders on the client after hydration; there is no server-rendered grid markup, because the element ships no declarative shadow DOM.

  • App Router: import the grid inside a client component, a file with 'use client' at the top. Importing it in a Server Component is a no-op on the server, since the element registers itself only in the browser, so nothing throws and the registry stays clean.
  • Pages Router and other SSR: the same rule. The component mounts and renders client-side after hydration.
'use client'

import { createApexGrid } from 'react-apex-grid'
import type { ColumnConfiguration } from 'apex-grid'

interface Row {
  id: number
  name: string
}

const Grid = createApexGrid<Row>()

export default function DataGrid({ rows }: { rows: Row[] }) {
  const columns: ColumnConfiguration<Row>[] = [{ key: 'id' }, { key: 'name' }]
  return <Grid data={rows} columns={columns} style={{ height: 480 }} />
}

Building the wrapper yourself

If you would rather not add the wrapper package, @lit/react bridges Lit events to React's synthetic event system directly. The factory pattern below is what react-apex-grid generates, narrowed to the events you name.

npm install apex-grid @lit/react

@lit/react is the current package name; the older @lit-labs/react was the preview name and is deprecated.

// apex-grid-wrapper.tsx
import React from 'react'
import { createComponent } from '@lit/react'
import { ApexGrid } from 'apex-grid'

ApexGrid.register()

export function createApexGrid<T extends object>() {
  return createComponent({
    tagName: 'apex-grid',
    elementClass: ApexGrid<T>,
    react: React,
    events: {
      onRowSelected:      'rowSelected',
      onRowSelecting:     'rowSelecting',
      onCellValueChanged: 'cellValueChanged',
      onFiltered:         'filtered',
      onSorted:           'sorted',
      onPageChanged:      'pageChanged',
    },
  })
}

The trade-off is maintenance: the events map is hand-written, so an event you forget to list is simply not available as a prop, and new events have to be added by hand.

Vue and Angular

Vue 3 and Angular 15 or newer consume the apex-grid element natively, with no wrapper package. See Vue and Angular.

Available events

With react-apex-grid every event below is available as an on<Event> prop, generated from the manifest. With a hand-rolled wrapper, only the events you list in the events map become props. ApexGrid dispatches the following.

Events emitted before an operation (the -ing / -Changing variants) are cancellable: call e.detail's cancellation or e.preventDefault() to stop the operation, and you can modify the expression before it runs.

Suggested propDOM eventCancellableFires
onRowSelectingrowSelectingyesBefore the row selection set changes
onRowSelectedrowSelectedAfter row selection changes
onCellValueChangingcellValueChangingyesBefore a cell value is committed
onCellValueChangedcellValueChangedAfter a cell value is committed
onCellValidationFailedcellValidationFailedA candidate cell value was rejected by validators
onRowEditStartedrowEditStartedA row entered edit mode (row edit mode)
onRowEditEndedrowEditEndedA row left edit mode (committed reports whether it saved)
onSortingsortingyesBefore a sort is applied
onSortedsortedAfter a sort is applied
onFilteringfilteringyesBefore a filter is applied
onFilteredfilteredAfter a filter is applied
onQuickFilterChangingquickFilterChangingyesBefore the quick-filter value is applied
onQuickFilterChangedquickFilterChangedAfter the quick filter is applied
onPageChangingpageChangingyesBefore a page or page-size change
onPageChangedpageChangedAfter a page or page-size change
onColumnPinningcolumnPinningyesBefore a column's pin position changes
onColumnPinnedcolumnPinnedAfter a column's pin position changes
onColumnMovingcolumnMovingyesBefore a column moves
onColumnMovedcolumnMovedAfter a column moves
onRowPinningrowPinningyesBefore a row is pinned, moved between bands, or unpinned
onRowPinnedrowPinnedAfter a row's pin state changes
onRowMovingrowMovingyesBefore a row moves
onRowMovedrowMovedAfter a row moves
onRowExpandingrowExpandingyesBefore the row-expansion set changes
onRowExpandedrowExpandedAfter a row-expansion change
onTreeRowExpandingtreeRowExpandingyesBefore the tree-row expansion set changes
onTreeRowExpandedtreeRowExpandedAfter a tree-row expansion change
onHistoryChangedhistoryChangedAfter the undo / redo stacks change
onStateChangedstateChangedAfter the grid's state changes (debounced)

Each handler receives the CustomEvent; read e.detail for the payload (typed per event, e.g. ApexRowSelectedEvent<T>, SortExpression<T>).