RevoGrid: A Million Rows Without Breaking a Sweat
If you have ever tried to dump a few hundred thousand rows into an HTML table, you already know how that story ends: the browser tab freezes, scrolling turns to molasses, and memory usage balloons until something gives. The naive approach paints one DOM node per cell, and the DOM was never built to babysit millions of nodes at once. RevoGrid exists to solve exactly this problem. It is a high-performance virtual data grid that renders only the cells inside the visible viewport, recycles them as you scroll, and stays responsive even when you bind a source array with a million-plus rows.
What makes RevoGrid interesting is its architecture. Rather than being a React-only component, it is built as a standards-based Web Component using StencilJS. The same compiled revo-grid custom element runs in React, Vue, Angular, and Svelte, with thin official wrappers exposing it idiomatically to each. For React, that wrapper is @revolist/react-datagrid, which maps React props to element properties and DOM events to friendly handlers. The core @revolist/revogrid package has zero runtime dependencies, is MIT-licensed, and is tree-shakeable, so it never drags a framework or utility library along for the ride. If you need an Excel-like spreadsheet UX for large data without paying a licensing fee, RevoGrid is a compelling place to start.
What Lives Inside the Grid
RevoGrid is genuinely batteries-included. The headline is performance, but the feature list reaches well beyond raw speed.
- Virtual scrolling for massive datasets, with no hard row limit and intelligent row recombination to minimize redraws.
- Multi-column sorting with per-column options, and advanced filtering including predefined, conditional, and custom filters.
- Range selection and editing with autofill, the Excel-style drag-fill that propagates a value across a selection.
- Copy and paste that speaks the Excel and Google Sheets clipboard format, including JSON.
- Pinned columns and rows (sticky left/right and top/bottom), plus column and row grouping with nested headers.
- Custom cell templates and editors, custom header templates, and per-cell properties.
- Accessibility and RTL support, with WAI-ARIA compliance and Excel-like keyboard navigation.
- Export to formats like CSV, and multiple themes (an Excel-like default plus Material light, dark, and compact variants).
A commercial Pro tier adds tree data, pivot tables, a formula engine, and server-side infinite scroll, but the MIT core covers the entire virtual-grid foundation and everything in the list above.
Getting It Into Your Project
Install the React wrapper. It pulls in the core grid as a pinned dependency, so you only need the one package.
npm install @revolist/react-datagrid
Or with yarn:
yarn add @revolist/react-datagrid
That is all you need to get rendering. When you want a particular look, you also import a theme stylesheet from the package, which we will touch on later.
Your First Grid in Three Props
A working grid needs only two things: a columns definition and a source array. The columns array describes the shape of the table, while source holds your row objects.
import { RevoGrid } from '@revolist/react-datagrid'
import { useState } from 'react'
const columns = [
{ prop: 'name', name: 'First' },
{ prop: 'details', name: 'Second' },
]
function App() {
const [source] = useState([
{ name: '1', details: 'Item 1' },
{ name: '2', details: 'Item 2' },
])
return <RevoGrid columns={columns} source={source} />
}
Each column maps its prop to a key on every row object, and name becomes the header label. Beyond those two fields, columns accept a rich set of options: sortable to enable header-click sorting, filter to turn on a filter menu, size to set a fixed width, and pin set to 'colPinStart' or 'colPinEnd' to freeze a column on either edge. Notice that columns is declared as a module-level constant and source lives in useState. That is not stylistic preference, it is the single most important habit when working with RevoGrid, and we will come back to why shortly.
Bringing Cells to Life With Templates
Plain text cells are fine, but real applications want status badges, formatted currency, or clickable buttons. RevoGrid gives you two ways to customize a cell. The friendly React path uses the Template helper, which wraps an ordinary React component so it can render inside a grid cell.
import { RevoGrid, Template, ColumnDataSchemaModel } from '@revolist/react-datagrid'
const Cell = ({ value }: Partial<ColumnDataSchemaModel>) => (
<div>
<strong>{value}</strong>
</div>
)
const columns = [
{ prop: 'name', name: 'First', cellTemplate: Template(Cell) },
]
The wrapped component receives the cell value along with other ColumnDataSchemaModel fields like the row model and index, so you have everything you need to render conditionally. There is one caveat worth internalizing: React components are heavier than the grid's native rendering path. The docs explicitly note that native VNode templates are faster at scale, so reach for Template() when you need genuine interactivity, not for every cell in a million-row grid.
When raw speed matters more than React ergonomics, drop to the native template, which uses an h hyperscript pragma and works identically across every framework wrapper.
{
prop: 'name',
cellTemplate: (h, { value }) =>
h('div', { style: { backgroundColor: 'red' }, class: { 'inner-cell': true } }, value || ''),
}
This little function returns a virtual node directly, skipping the React reconciliation overhead entirely. It is the right tool for coloring thousands of cells based on their value without paying a performance tax.
Driving the Grid Imperatively
Declarative props handle most of what you need, but sometimes you want to command the grid directly: scroll to a specific row, open a cell for editing, or grab the currently visible data. RevoGrid exposes an imperative API on the element instance, which you reach through a React ref.
import { RevoGrid } from '@revolist/react-datagrid'
import { useRef } from 'react'
function Editor() {
const gridRef = useRef<any>(null)
const focusFirstCell = async () => {
await gridRef.current?.scrollToRow?.(0)
await gridRef.current?.setCellEdit?.(0, 'price')
}
return (
<>
<button onClick={focusFirstCell}>Edit first price</button>
<RevoGrid ref={gridRef} columns={columns} source={source} />
</>
)
}
These methods are asynchronous because they coordinate with the grid's internal rendering, so awaiting them keeps your interactions predictable. The same ref gives you access to helpers like getVisibleSource(), refresh operations, and clipboard methods, letting you build toolbars and keyboard shortcuts that feel native to a spreadsheet.
Reacting to Edits and Filters
Because RevoGrid is a web component underneath, it communicates outward through DOM CustomEvents rather than React's callback-prop convention. The wrapper surfaces these to you, and the meaningful data always lives on event.detail. The common events are beforeedit, afteredit, beforefilterapply, and beforesourceset. A typical pattern is to listen for afteredit and fold the change back into your own state, or to intercept beforeedit to validate and optionally cancel an edit.
import { RevoGrid } from '@revolist/react-datagrid'
import { useState } from 'react'
function EditableGrid() {
const [source, setSource] = useState(initialRows)
const handleAfterEdit = (e: CustomEvent) => {
const { rowIndex, prop, val } = e.detail
setSource((rows) => {
const next = [...rows]
next[rowIndex] = { ...next[rowIndex], [prop]: val }
return next
})
}
return (
<RevoGrid
columns={columns}
source={source}
onAfteredit={handleAfterEdit}
/>
)
}
This keeps your React state as the single source of truth while the grid handles the editing UX. If you wanted validation, you would attach a beforeedit handler and call e.preventDefault() on the event whenever a value fails your rules, stopping the edit before it ever touches your data.
The Gotchas Worth Knowing Up Front
RevoGrid is powerful, but its web-component nature creates a few sharp edges that are far easier to avoid than to debug later.
The biggest one is reference stability for columns and source. Because these props are forwarded to web-component properties and the grid reacts to identity changes, passing a freshly created array on every render (an inline columns={[...]} or a .map() in the render body) can trigger infinite re-renders and thrashing. Always memoize them with useMemo, useState, or a module-level constant, exactly as the examples above do.
The second is server-side rendering. RevoGrid depends on custom elements and the DOM, so it must run client-side only. In Next.js, render it inside a client component and use a dynamic import with SSR disabled, otherwise it will throw on the server.
import dynamic from 'next/dynamic'
const RevoGrid = dynamic(
() => import('@revolist/react-datagrid').then((m) => m.RevoGrid),
{ ssr: false },
)
Finally, remember that styling crosses a component boundary. You import a theme CSS package to get the grid's look, and custom styling flows through the grid's theming and cell-class hooks rather than arbitrary external selectors. Plan your styling around the provided theme system instead of fighting it from the outside.
Where RevoGrid Fits
It helps to know when RevoGrid is the right call. Compared to AG Grid, RevoGrid is lighter, dependency-free, and MIT-licensed for its full core, where AG Grid gates grouping and pivoting behind a paid enterprise license. Compared to TanStack Table, which is headless and renders nothing, RevoGrid is the opposite: a fully rendered grid with built-in virtualization and editing out of the box. Reach for TanStack Table when you want total markup control; reach for RevoGrid when you want a working spreadsheet UI immediately. And compared to Handsontable, the closest spiritual competitor, RevoGrid delivers a similar Excel-like editing and clipboard experience under a permissive MIT license, which is a meaningful difference for commercial projects.
You would pick RevoGrid when you need to render tens of thousands to millions of rows with smooth virtual scrolling, when you want an Excel-like editing experience without buying a license, and when you value a zero-dependency package that can work identically across frameworks if your stack ever shifts. You might look elsewhere if you need a tiny headless table you fully control, the deepest enterprise pivot tooling, or you simply cannot accommodate the web-component interop and SSR caveats.
For the broad middle ground, though, where you just need a fast, capable, free spreadsheet grid in your React app, RevoGrid is hard to beat. Bind a big array, memoize your config, and let the virtual viewport do the heavy lifting while your users scroll through a million rows like it is nothing.