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 prop | DOM event | Cancellable | Fires |
|---|---|---|---|
onRowSelecting | rowSelecting | yes | Before the row selection set changes |
onRowSelected | rowSelected | After row selection changes | |
onCellValueChanging | cellValueChanging | yes | Before a cell value is committed |
onCellValueChanged | cellValueChanged | After a cell value is committed | |
onCellValidationFailed | cellValidationFailed | A candidate cell value was rejected by validators | |
onRowEditStarted | rowEditStarted | A row entered edit mode (row edit mode) | |
onRowEditEnded | rowEditEnded | A row left edit mode (committed reports whether it saved) | |
onSorting | sorting | yes | Before a sort is applied |
onSorted | sorted | After a sort is applied | |
onFiltering | filtering | yes | Before a filter is applied |
onFiltered | filtered | After a filter is applied | |
onQuickFilterChanging | quickFilterChanging | yes | Before the quick-filter value is applied |
onQuickFilterChanged | quickFilterChanged | After the quick filter is applied | |
onPageChanging | pageChanging | yes | Before a page or page-size change |
onPageChanged | pageChanged | After a page or page-size change | |
onColumnPinning | columnPinning | yes | Before a column's pin position changes |
onColumnPinned | columnPinned | After a column's pin position changes | |
onColumnMoving | columnMoving | yes | Before a column moves |
onColumnMoved | columnMoved | After a column moves | |
onRowPinning | rowPinning | yes | Before a row is pinned, moved between bands, or unpinned |
onRowPinned | rowPinned | After a row's pin state changes | |
onRowMoving | rowMoving | yes | Before a row moves |
onRowMoved | rowMoved | After a row moves | |
onRowExpanding | rowExpanding | yes | Before the row-expansion set changes |
onRowExpanded | rowExpanded | After a row-expansion change | |
onTreeRowExpanding | treeRowExpanding | yes | Before the tree-row expansion set changes |
onTreeRowExpanded | treeRowExpanded | After a tree-row expansion change | |
onHistoryChanged | historyChanged | After the undo / redo stacks change | |
onStateChanged | stateChanged | After 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>).