# Infinite Table Documentation
> Developer Documentation for Infinite Table, your go-to React DataGrid component to handle huge amounts of data
Canonical page: https://infinite-table.com/docs/
## What is Infinite Table?
Infinite Table is a React DataGrid component for displaying virtualized tabular data.
It helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:
- [ sorting](https://infinite-table.com/docs/learn/sorting/overview.md)
- [ row grouping](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows.md) - both server-side and client-side
- [ pivoting](https://infinite-table.com/docs/learn/grouping-and-pivoting/pivoting/overview.md) - both server-side and client-side
- [ aggregations](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows.md#aggregations)
- [ live pagination](https://infinite-table.com/docs/learn/working-with-data/live-pagination.md)
- [ lazy loading](https://infinite-table.com/docs/learn/working-with-data/lazy-loading.md)
- [ keyboard navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-cells.md)
- [ fixed and flexible columns](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md)
- [ column grouping](https://infinite-table.com/docs/learn/columns/column-grouping.md)
- [ filtering](https://infinite-table.com/docs/learn/filtering/index.md)
- [ theming](https://infinite-table.com/docs/learn/theming/index.md)
## Installation
Installation could not be more straightforward - just one npm command:
npm i @infinite-table/infinite-react
## ❤️ TypeScript
Infinite Table is fully typed and offers you a great developer experience, to help you get up and running quickly.
> The TypeScript typings file is included in the npm package - you don't have to download an additional **@types** package
Read more about how to use our TypeScript types
## 📄 Extensive Documentation
We're aware good documentation is a must and are updating our documentation as we add new features. Head to [our getting started](https://infinite-table.com/docs/learn/getting-started/index.md) guide to get up and running quickly.
## 🏢 Enterprise-Ready
Infinite Table is ready to power your enterprise apps, as it supports advanced [data fetching](https://infinite-table.com/docs/learn/working-with-data/index.md#data-loading-strategies), [filtering](https://infinite-table.com/docs/learn/filtering/index.md), [sorting](https://infinite-table.com/docs/learn/sorting/overview.md), [grouping](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows.md), [pivoting](https://infinite-table.com/docs/learn/grouping-and-pivoting/pivoting/overview.md), [aggregations](https://infinite-table.com/docs/learn/grouping-and-pivoting/group-aggregations.md), [live pagination](https://infinite-table.com/docs/learn/working-with-data/live-pagination.md), [lazy loading](https://infinite-table.com/docs/learn/working-with-data/lazy-loading.md) - all of those with support for both client-side and server-side implementations.
You can choose to leverage our built-in implementations in the browser, or you can process your data on the server with full support from our-side.
### 🔒 Secure by Default
We take security seriously and only have a total of 3 dependencies in our full dependency graph - and this number will only go down.
### 📦 Small Bundle Size
Our bundle size is under `300kB` and we're dedicated to [keeping it small](https://bundlephobia.com/package/@infinite-table/infinite-react).
See our bundle size in BundlePhobia
### 🧪 Automated End-to-End Tests
Our releases are automated and, we have full end-to-end tests that ensure we're delivering to our standards.
Real-browser tests help us move with confidence and continue to ship great features.
Check out our end-to-end tests in GitHub
## 🎨 Themable
`Infinite Table` is fully customizable, via CSS variables.
It ships with both a **light** and a **dark** theme - all you have to do is import the CSS file from the package.
```ts
import '@infinite-table/infinite-react/index.css';
// This file includes both the light and the dark themes.
```
Read how to use themes and **CSS variables** to customize every aspect of Infinite Table
---
# Infinite Table DevTools
> Guide on using the Chrome DevTools Extension for the Infinite Table React DataGrid
Canonical page: https://infinite-table.com/docs/devtools
We're happy to announce that [Infinite Table DevTools extension](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) is now live - [install it here!](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa)
To see the extension on a live demo, head to the [chrome webstore](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) to download the extension.
Then visit [our live demo page](https://infinite-table.com/full-demo) and open your browser devtools - you should see the "Infinite Table" devtool tab. Click it and enjoy interacting with the DataGrid!
To see an Infinite Table instance in the devtools, specify the [`debugId`](https://infinite-table.com/docs/reference/infinite-table-props.md#debugId) prop.
```tsx {2}
```
Infinite Table is the first DataGrid with a Chrome DevTools extension. Starting with version `7.0.0` of Infinite, you can specify the `debugId` property on the `` instance and it will be picked up by the devtools.
```tsx {16}
const columns = {
name: {
field: 'firstName',
},
lastName: {
field: 'lastName',
},
age: {
field: 'age',
},
}
const App = () => {
return
}
```
If you have multiple instances, each with a unique `debugId` property, they will all show up
---
# Working with Columns
> Define columns to configure your Infinite Table React DataGrid - fixed and flexible columns, resize, column groups and more
Canonical page: https://infinite-table.com/docs/learn/columns/
Columns are a central feature in `InfiniteTable`.
You define columns as a an object, with keys being column ids while values are the column definitions.
You then use them in the `columns` prop in your `InfiniteTable` component.
The [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) prop is typed either as
- `Record>`
- or `InfiniteTablePropColumns`, which is an alias for the type above
In `InfiniteTable`, columns are identified by their key in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object. **We'll refer to this as the column id**.
The column ids are used in many places - like defining the [column order](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder), column pinning, column visibility, etc.
```ts
export type Employee = {
id: number;
companyName: string;
firstName: string;
lastName: string;
country: string;
city: string;
department: string;
team: string;
salary: number;
};
// InfiniteTableColumn is a generic type, you have to bind it to a specific data-type
import { InfiniteTableColumn } from '@infinite-table/infinite-react';
// we're binding it here to the `Employee` type
// which means the `column.field` has to be `keyof Employee`
export const columns: Record> = {
'firstName':
{
field: 'firstName',
header: 'First Name',
},
'country':
{
field: 'country',
},
'city':
{
field: 'city'
},
'salary':
{
field: 'salary',
type: 'number'
},
}
```
It's very important to remember you should not pass a different reference of a prop on each render. `` is a optimized to only re-render when props change - so if you change the props on every re-render you will get a performance penalty.
You should use `React.useCallback` / `React.useMemo` / `React.useState` to make sure you only update the props you pass down to `InfiniteTable` when you have to.
**Example: Basic Column Configuration**
```ts
import {
InfiniteTable,
DataSource,
type InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
export type Employee = {
id: number;
companyName: string;
companySize: string;
firstName: string;
lastName: string;
country: string;
countryCode: string;
city: string;
streetName: string;
streetNo: string;
department: string;
team: string;
salary: number;
age: number;
email: string;
};
export const columns: Record> = {
firstName: {
field: 'firstName',
header: 'First Name',
},
country: {
field: 'country',
header: 'Country',
columnGroup: 'location',
},
city: {
field: 'city',
header: 'City',
columnGroup: 'address',
},
salary: {
field: 'salary',
type: 'number',
header: 'Salary',
},
department: {
field: 'department',
header: 'Department',
},
team: {
field: 'team',
header: 'Team',
},
company: { field: 'companyName', header: 'Company' },
companySize: {
field: 'companySize',
header: 'Company Size',
},
};
export default function App() {
return (
data={dataSource} primaryKey="id">
debugId="basic-columns-example"
columns={columns}
columnDefaultWidth={200}
/>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
.then((r) => r.json())
.then((data: Employee[]) => data);
};
```
Find out how to render custom content inside columns or even take full control of column cells and header.
## Column Types
Column types allow you to customize column behavior and appearance for multiple columns at once. Most of the properties available for columns are also available for column types - for a full list, see [columnTypes](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes) reference.
There are two special [column types](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) for now, but more are coming soon:
- `default` - all columns have this type, if not otherwise specified. The type does not contain any configuration, but allows you to define it and apply common configuration to all columns.
- `number` - if specified on a column (in combination with local uncontrolled sorting), the column will be sorted numerically.
Find out how to use column types to customize the appearance and behaviour of your columns.
## Column Order
The implicit column order is the order in which columns have been defined in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object. You can however control that explicitly by using the `columnOrder: string[]` prop.
```tsx
const columnOrder = ['firstName','id','curency']
const App = () => {
return primaryKey={"id"} dataSource={...}>
columnOrder={columnOrder}
onColumnOrderChange={(columnOrder: string[]) => {}}
/>
}
```
The [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) prop is an array of strings, representing the column ids. A column id is the key of the column in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object.
The [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) array can contain identifiers that are not yet defined in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) Map, or can contain duplicate ids. This is a feature, not a bug. We want to allow you to use the [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) in a flexible way so it can define the order of current and future columns.
[`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) is a controlled prop. For the uncontrolled version, see [`defaultColumnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnOrder)
When using controlled [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder), make sure you also update the order by using the [`onColumnOrderChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnOrderChange) callback prop.
**Example: Column Order demo, with firstName col displayed twice**
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';
export type Employee = {
id: number;
companyName: string;
companySize: string;
firstName: string;
lastName: string;
country: string;
countryCode: string;
city: string;
streetName: string;
streetNo: string;
department: string;
team: string;
salary: number;
age: number;
email: string;
};
export const columns: InfiniteTablePropColumns = {
firstName: {
field: 'firstName',
header: 'First Name',
},
country: {
field: 'country',
header: 'Country',
columnGroup: 'location',
},
city: {
field: 'city',
header: 'City',
columnGroup: 'address',
},
salary: {
field: 'salary',
type: 'number',
header: 'Salary',
},
department: {
field: 'department',
header: 'Department',
},
team: {
field: 'team',
header: 'Team',
},
company: { field: 'companyName', header: 'Company' },
companySize: {
field: 'companySize',
header: 'Company Size',
},
};
export default function App() {
const [columnOrder, setColumnOrder] = useState([
'firstName',
'country',
'team',
'company',
'department',
'companySize',
]);
return (
<>
Current column order:{' '}
{columnOrder.join(', ')}.
Drag column headers to reorder.
data={dataSource} primaryKey="id">
debugId="columnOrder-example"
columns={columns}
columnOrder={columnOrder}
onColumnOrderChange={setColumnOrder}
columnDefaultWidth={200}
/>
>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
.then((r) => r.json())
.then((data: Employee[]) => data);
};
```
By keeping the column order simple, namely an array of strings, ordering becomes much easier.
The alternative would be to make `columns` an array, which most DataGrids do - and whenever they are reordered, a new `columns` array would be needed.
---
# Column Styling
> Styling columns in the InfiniteTable React DataGrid via both style and className properties.
Canonical page: https://infinite-table.com/docs/learn/columns/cell-and-column-styling
## Using the column `style`
The most straightforward way to style the cells in a column is to use the [column.style](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style) property as an object.
```ts title="Styling a column in the DataGrid"
const column = {
firstName: {
style: {
color: 'red',
fontWeight: 'bold',
},
},
};
```
```tsx
import * as React from 'react';
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
const columns: InfiniteTablePropColumns = {
id: {
field: 'id',
defaultWidth: 80,
},
name: {
field: 'firstName',
header: 'Name',
style: {
color: 'red',
fontWeight: 'bold',
},
},
};
type Developer = {
id: number;
firstName: string;
lastName: string;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const domProps = {
style: {
minHeight: 300,
},
};
export default function App() {
return (
primaryKey="id" data={dataSource}>
debugId="column-style-object-example"
domProps={domProps}
columns={columns}
/>
);
}
```
The [column.style](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style) property can either be an object (of type `React.CSSProperties`) or a function that returns an object (of the same type).
Using functions for the [column.style](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style) property allows you to style the cells based on the cell's value or other properties.
```ts {6} title="Styling a column using a style function"
const columns = {
salary: {
field: 'salary',
type: 'number',
style: ({ value, data, column, rowInfo }) => {
return {
color: value && value > 100_000 ? 'red' : 'tomato',
};
},
},
};
```
**Example: Using column.style as a function**
```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
id: {
field: 'id',
defaultWidth: 80,
},
firstName: { field: 'firstName' },
salary: {
field: 'salary',
type: 'number',
style: ({ value }) => {
return {
color: value && value > 100_000 ? 'red' : 'tomato',
};
},
},
preferredLanguage: { field: 'preferredLanguage' },
stack: { field: 'stack' },
country: { field: 'country' },
age: { field: 'age', type: 'number' },
currency: { field: 'currency', type: 'number' },
};
export default function App() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="column-style-fn-example"
columns={columns}
columnDefaultWidth={200}
/>
>
);
}
```
If defined as a function, the [column.style](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style) accepts an object as a parameter, which has the following properties:
- `column` - the current column where the style is being applied
- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial`. For rows not yet loaded (because of batching being used), it will be `null`.
- `rowInfo` - the information about the current row - see [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.
- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property
## Using the column `className`
Mirroring the behavior already described for the [column.style](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style) property, the [column.className](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.className) property can be used to apply a CSS class to the cells in a column.
It can be used as a string or a function that returns a string.
```ts title="Styling a column using column.className"
const columns = {
firstName: {
className: 'first-name-column',
},
};
```
**Example: Using column.className as an string**
```tsx files=["column-className-string-example.page.tsx","coloring.module.css"]
```
Using functions for the [column.className](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.className) property allows you to style the cells based on the cell's data/value/rowInfo etc.
```ts {6} title="Styling a column using a className function"
const columns = {
salary: {
field: 'salary',
type: 'number',
className: ({ value, data, column, rowInfo }) => {
return value && value > 100_000 ? 'red-color' : 'tomato-color',
},
},
}
```
**Example: Using column.className as a function**
```tsx files=["column-className-fn-example.page.tsx","coloring.module.css"]
```
---
# Column Groups
> Columns can be grouped with multiple levels of nesting thus making Infinite Table DataGrid a powerful tool for data analysts
Canonical page: https://infinite-table.com/docs/learn/columns/column-grouping
Specify column groups via the controlled [`columnGroups`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnGroups) (or uncontrolled [`defaultColumnGroups`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnGroups)) prop.
The value is an object, with keys being the group id and value being the group description.
```tsx title="defining-column-groups"
const columnGroups: Record = {
'contact info': { header: 'Contact info' },
// `street` column group belongs to the `address` columnGroup
street: { header: 'street', columnGroup: 'address' },
location: { header: 'location', columnGroup: 'address' },
// this is a top-level group
address, { header: 'Address' }
}
```
A column group can have a parent column group, specified by the [`columnGroups.columnGroup`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnGroups.columnGroup) property. The same goes for a column - columns can have [columnGroup](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.columnGroup) as well.
```tsx title="defining-columns-with-groups"
const columns: Record> = {
id: { field: 'id' },
// `streetNo` column belongs to the `street` columnGroup
streetNo: { field: 'streetNo', columnGroup: 'street' },
city: { field: 'city', columnGroup: 'location' },
streetName: { field: 'streetName', columnGroup: 'street' },
firstName: { field: 'firstName' },
country: { field: 'country', columnGroup: 'location' },
region: { field: 'region', columnGroup: 'location' },
email: { field: 'email', columnGroup: 'contact info' },
phone: { field: 'phone', columnGroup: 'contact info' },
};
```
## Column groups in action
**Example**
```tsx files=["column-groups-example.page.tsx","column-groups-data.ts"]
```
---
# Column Headers
> Configure column headers with custom column header, custom sort icon, menu icon and more.
Canonical page: https://infinite-table.com/docs/learn/columns/column-headers
Column headers have the same level of customization as column cells - you can fully control what is being rendered and when. Here's a summary of the things you can do in the column header:
- customize the header label of a column
- specify custom sort icon
- configure and customize the menu icon
- configure the column selection chechbox (for columns configured to display a selection checkbox)
- customize the order of all of the above, and select which ones should be included
## Column Header Label
By default, the label displayed for the column header is the [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) the column is bound to. If you want to customize this, use the [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) property.
```tsx
type Developer = {
id: string;
firstName: string;
lastName: string;
age: number;
};
const columns: InfiniteTablePropColumns = {
id: {
field: 'id', // will be used as default label in column header
defaultWidth: 100,
},
name: {
header: 'First and Last Name', // custom column header label
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
},
};
```
**Example: Simple table with both default and custom column headers**
```ts
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
const columns: InfiniteTablePropColumns = {
id: {
field: 'id', // will be used as default label in column header
defaultWidth: 100,
},
name: {
header: 'First and Last Name', // custom column header label
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
},
};
type Developer = {
id: number;
firstName: string;
lastName: string;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const domProps = {
style: {
minHeight: 300,
},
};
export default function App() {
return (
primaryKey="id" data={dataSource}>
debugId="column-header-example"
domProps={domProps}
columns={columns}
/>
);
}
```
Having the [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) property be a strin value is useful but when you want more flexibility, you can use a function instead.
When [the column header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) is a function, it is called with an object that contains the following properties:
- `column` - the current column object. NOTE: it's not the same as the column object you passed to the [columns](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) prop - but rather an enhanced version of that, which contains additional properties and computed values. It is called a "computed" column - typed as `InfiniteTableComputedColumn`.
- `columnsMap` - a map of all computed columns available in the table, keyed by the column id. This is useful if at runtime you need access to other columns in the table. NOTE: this map does not contain only the visible columns, but rather ALL the columns.
- `columnSortInfo` - the sorting information for the current column, or `null` if the column is not sorted.
- `api` - a reference to the table [API](https://infinite-table.com/docs/reference/api/index.md) object.
- `columnApi` - a reference to the table [Column API](https://infinite-table.com/docs/reference/column-api/index.md) object for bound to the current column.
- `allRowsSelected: boolean`
- `someRowsSelected: boolean`
- `renderBag` - more on that below - used to reference changes between the different render functions of the column header (those functions are the column header rendering pipeline described in the next section).
All the render props exposed for the rendering pipeline of the column header are called with the same object as the first argument.
Having the [column header](https://infinite-table.com/docs/reference/infinite-table-props.md#column.header) as a function and having access to the state of the column and of the table allows you to create very dynamic column headers that accurately reflect column state.
## Column Header Rendering Pipeline
The rendering pipeline of the column header is similar to the one of the column cells.
It's a series of functions defined on the column that are called while rendering elements found in the column header (the header label, the sort and menu icons, the filtering icon, the selection checkbox).
All of the functions that are part of the column header rendering pipeline are called with the same object as the first argument - the shape of this object is described in the previous section.
- [renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon)
- [renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon)
- [renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon)
- [renderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox)
- [renderHeaderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeaderSelectionCheckBox)
- [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header)
If you want to customize any of the above, use the corresponding function.
For even more control, the last function in the pipeline that gets called is the [column.renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function.
This function is called with the same object as the first argument, but it also has a `renderBag` property that contains the result of all the previous functions in the pipeline (eg: `renderBag.sortIcon` - the result of the [renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon) call, `renderBag.filterIcon` - the result of the [renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) call, etc).
So if you specify a custom [renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function, it's up to you to use the results of the previous functions in the pipeline, in order to fully take control of the column header.
#### Available properties on the renderBag
The `renderBag` object contains the following properties available to the render functions of the column header:
- `header` - the label of the column header.
- `sortIcon` - the default sort icon
- `filterIcon` - the filter icon - displayed when the current column is used in filtering
- `filterEditor` - the current filter editor
- `menuIcon` - the menu icon that can be clicked to open the column menu
- `selectionCheckBox` - the selection check box - displays the current selection status and controls the selection for all rows.
- `all` - all of the above combined together in a `React.Fragment`.
### Customizing the Sort Icon
For customizing the sort icon, use the [column.renderSortIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon) function.
Inside that function you can either use the object passed as a parameter to get information about the sort state of the column
```tsx {1} title="Customizing_the_column_sort_icon"
renderSortIcon({ columnSortInfo }) {
if (!columnSortInfo) {
return ' 🤷♂️';
}
return columnSortInfo.dir === 1 ? '▲' : '▼';
}
```
or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.
```tsx {8} title="Customizing_the_column_sort_icon"
import {
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
/// ...
renderSortIcon(){
const { columnSortInfo } = useInfiniteHeaderCell();
if (!columnSortInfo) {
return ' 🤷♂️';
}
return columnSortInfo.dir === 1 ? '▲' : '▼';
},
```
**Example: Custom sort icon for the name column**
```ts
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
import * as React from 'react';
const columns: InfiniteTablePropColumns = {
id: {
field: 'id', // will be used as default label in column header
defaultWidth: 100,
},
name: {
header: 'Name', // custom column header label
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
renderSortIcon: () => {
const { columnSortInfo } = useInfiniteHeaderCell(); // eslint-disable-line
if (!columnSortInfo) {
return ' 🤷♂️';
}
return columnSortInfo.dir === 1 ? '▲' : '▼';
},
},
};
type Developer = {
id: number;
firstName: string;
lastName: string;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const domProps = {
style: {
minHeight: 300,
},
};
export default function App() {
return (
primaryKey="id" data={dataSource}>
debugId="column-sort-icon-example"
domProps={domProps}
columns={columns}
/>
);
}
```
### Customizing the Menu Icon
For customizing the menu icon, use the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) function.
Inside that function you can either use the object passed as a parameter to get information about the column
```tsx {1} title="Customizing_the_menu_icon"
renderMenuIcon({ column }) {
return `🔧 ${column.id}`;
}
```
or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.
```tsx {8} title="Customizing_the_menu_icon"
import {
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
/// ...
renderMenuIcon(){
const { column } = useInfiniteHeaderCell();
return `🔧 ${column.id}`;
},
```
**Example: Custom menu icon for the name and age columns**
Hover over the header for the `Name` and `Age` columns to see the custom menu icon.
Also, the id column has `renderMenuIcon: false` set, so it doesn't show a column menu at all.
```ts
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
const columns: InfiniteTablePropColumns = {
id: {
field: 'id',
defaultWidth: 80,
renderMenuIcon: false,
},
name: {
header: 'Name', // custom column header label
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
// custom menu icon
renderMenuIcon: () =>
🌎
,
},
age: {
field: 'age',
header: 'Age',
renderMenuIcon: ({ column }) => {
return `🔧 ${column.id}`;
},
},
};
type Developer = {
id: number;
firstName: string;
lastName: string;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const domProps = {
style: {
minHeight: 300,
},
};
export default function App() {
return (
primaryKey="id" data={dataSource}>
debugId="column-menu-icon-example"
domProps={domProps}
columns={columns}
/>
);
}
```
If you don't want to show a column menu (icon) at all, you can set the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) prop to `false`.
Also, see the [column.renderMenuIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) docs for an example on how to use the api to open the column menu.
### Customizing the Filter Icon
For customizing the filter icon, use the [column.renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) function.
Inside that function you can either use the object passed as a parameter to get information about the `filtered` state of the column
```tsx {1} title="Customizing_the_filter_icon"
renderFilterIcon({ filtered }) {
return filtered ? '🔍' : '';
}
```
or you can use the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook to get the same information.
```tsx {8} title="Customizing_the_menu_icon"
import {
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
/// ...
renderMenuIcon(){
const { filtered } = useInfiniteHeaderCell();
return filtered ? '🔥' : '';
},
```
In addition, you can use the `filtered` property in the [column.header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) function to determine if the column is filtered or not and render a different header label.
If specified, the [column.renderFilterIcon](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderFilterIcon) function prop is called even if the column is not currently filtered.
**Example: Custom filter icons for salary and name columns**
The `salary` column will show a bolded label when filtered.
The `firstName` column will show a custom filter icon when filtered.
```ts
import * as React from 'react';
import {
DataSourceData,
InfiniteTable,
InfiniteTablePropColumns,
DataSource,
} from '@infinite-table/infinite-react';
type Developer = {
id: number;
firstName: string;
lastName: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
salary: number;
};
const data: DataSourceData = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
id: {
field: 'id',
type: 'number',
defaultWidth: 100,
},
salary: {
field: 'salary',
type: 'number',
header: ({ filtered }) => {
return filtered ? Salary : 'Salary';
},
renderFilterIcon: () => {
return null;
},
},
firstName: {
field: 'firstName',
renderFilterIcon: ({ filtered }) => {
return filtered ? '🔥' : '';
},
},
stack: { field: 'stack' },
currency: { field: 'currency' },
};
const domProps = {
style: {
height: '100%',
},
};
export default () => {
return (
<>
data={data}
primaryKey="id"
defaultFilterValue={[]}
filterDelay={0}
filterMode="local"
>
debugId="column-filter-icon-example"
domProps={domProps}
columnDefaultWidth={150}
columnMinWidth={50}
columns={columns}
/>
>
);
};
```
#### Changing the display of filters
Infinite Table allows very deep cusstomization of the column header, including the filters.
For example, you might not want to display the column filters under the column header, but rather in a separate menu popover.
This section shows how to do that. You can use [showColumnFilters=false](https://infinite-table.com/docs/reference/infinite-table-props.md#showColumnFilters) to hide the filters from under the column header.
Next, you can use the [column.renderHeader](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeader) function to render a custom filter icon that opens a filter popover when clicked.
You don't need to re-implement the filter editor, you have acces to it via the `renderBag.filterEditor` property. The code below shows how to do this.
**Example: Custom display of column filters**
```ts
import * as React from 'react';
import {
DataSourceData,
InfiniteTable,
InfiniteTablePropColumns,
DataSource,
InfiniteTableColumn,
useInfiniteHeaderCell,
alignNode,
useInfinitePortalContainer,
} from '@infinite-table/infinite-react';
import { createPortal } from 'react-dom';
type Developer = {
id: number;
firstName: string;
lastName: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
salary: number;
};
const data: DataSourceData = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const FilterIcon = () => (
);
function ColumnFilterMenuIcon() {
const {
renderBag,
htmlElementRef: alignToRef,
column,
} = useInfiniteHeaderCell();
const portalContainer = useInfinitePortalContainer();
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
if (!domRef.current || !alignToRef.current) {
return;
}
alignNode(domRef.current, {
alignTo: alignToRef.current,
alignPosition: [['TopRight', 'BottomRight']],
});
});
const domRef = React.useRef(null);
return (
);
}
const customHeaderWithFilterMenu: InfiniteTableColumn['renderHeader'] = ({
renderBag,
}) => {
return (
<>
{renderBag.header}
{renderBag.filterIcon}
{renderBag.menuIcon}
>
);
};
const columns: InfiniteTablePropColumns = {
id: {
field: 'id',
type: 'number',
defaultWidth: 100,
renderHeader: customHeaderWithFilterMenu,
},
salary: {
field: 'salary',
type: 'number',
renderHeader: customHeaderWithFilterMenu,
},
firstName: {
field: 'firstName',
renderHeader: customHeaderWithFilterMenu,
},
stack: { field: 'stack', renderHeader: customHeaderWithFilterMenu },
currency: { field: 'currency', renderHeader: customHeaderWithFilterMenu },
};
export default () => {
return (
<>
data={data}
primaryKey="id"
defaultFilterValue={[]}
shouldReloadData={{
filterValue: false,
sortInfo: false,
groupBy: false,
pivotBy: false,
}}
>
debugId="custom-column-filter-display-example"
showColumnFilters={false}
columnDefaultWidth={150}
columnMinWidth={50}
columns={columns}
/>
>
);
};
```
### Customizing the Selection Checkbox
For customizing the selection checkbox in the column header, use the [column.renderHeaderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderHeaderSelectionCheckBox) function.
If you want another column, other than the group column, to show a selection checkbox, you have to also set the [column.renderSelectionCheckBox](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox) prop to `true`.
**Example: Custom header checkbox selection for columns**
The group column, as well as the `stack` column display a custom selection checkbox in the column header.
```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
InfiniteTableProps,
InfiniteTablePropColumns,
DataSourceProps,
} from '@infinite-table/infinite-react';
import * as React from 'react';
const columns: InfiniteTablePropColumns = {
country: {
field: 'country',
},
firstName: {
field: 'firstName',
defaultHiddenWhenGroupedBy: '*',
},
stack: {
renderSelectionCheckBox: true,
renderHeaderSelectionCheckBox: ({ renderBag }) => {
// render the default value and decorate it
return [{renderBag.selectionCheckBox}];
},
field: 'stack',
},
age: { field: 'age' },
id: { field: 'id' },
preferredLanguage: {
field: 'preferredLanguage',
},
canDesign: {
field: 'canDesign',
},
};
const defaultGroupBy: DataSourceProps['groupBy'] = [
{
field: 'canDesign',
},
{
field: 'stack',
},
{
field: 'preferredLanguage',
},
];
const groupColumn: InfiniteTableProps['groupColumn'] = {
field: 'firstName',
renderHeaderSelectionCheckBox: ({ renderBag }) => {
// render the default value and decorate it
return [{renderBag.selectionCheckBox}];
},
defaultWidth: 300,
};
const domProps = {
style: {
flex: 1,
minHeight: 500,
},
};
export default function App() {
return (
data={dataSource}
groupBy={defaultGroupBy}
selectionMode="multi-row"
primaryKey="id"
>
debugId="column-header-selection-checkbox-example"
columns={columns}
domProps={domProps}
groupColumn={groupColumn}
columnDefaultWidth={150}
/>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
email: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
```
---
# Column Menus
> Columns have menus that allow quick actions - the menus can be customized or hidden altogether.
Canonical page: https://infinite-table.com/docs/learn/columns/column-menus
All columns in the Infinite Table have a default menu, which can be customized or hidden altogether.
## Customise the menu items
To customize the column menu (for all columns, or for a specific column), use the [`getColumnMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getColumnMenuItems) prop. This function is called with an array of menu items (which are the default items) and it should the final array of menu items - so you can return the default items as is, or you can adjust the default items to fit your needs.
```tsx title="Customizing-column-menu"
function getColumnMenuItems(items, { column }) {
if (column.id === 'firstName') {
// you can adjust the default items for a specific column
items.splice(0, 0, {
key: 'firstName',
label: 'First name menu item',
onClick: () => {
console.log('Hey there!');
},
});
}
// or for all columns
items.push({
key: 'hello',
label: 'Hello World',
onClick: () => {
alert('Hello World from column ' + column.id);
},
});
return items;
}
```
[`getColumnMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getColumnMenuItems) can return an empty array, in which case, the column menu will not be shown - however, people will still be able to click the menu icon to trigger the column context menu.
If you want to dynamically decide whether a column should show a menu or not, you can use the [`columns.renderMenuIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) prop.
**Example: Custom column menu items and custom menu icon**
In this example, the currency and preferredLanguage columns have a custom icon for triggering the column context menu.
In addition, the `preferredLanguage` column has a custom header that shows a button for triggering the column context menu.
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
currency: {
field: 'currency',
// custom menu icon
renderMenuIcon: () =>
🌎
,
},
preferredLanguage: {
field: 'preferredLanguage',
defaultWidth: 350,
header: ({ columnApi, renderLocation }) => {
// if we're inside the column menu with all columns, return only the col name
if (renderLocation === 'column-menu') {
return 'Preferred Language';
}
// but for the real column header
// return this custom content
return (
<>
Preferred Language{' '}
>
);
},
// custom menu icon
renderMenuIcon: () =>
🌎
,
},
salary: {
field: 'salary',
// hide the menu icon
renderMenuIcon: false,
},
country: {
field: 'country',
},
id: { field: 'id', defaultWidth: 80, renderMenuIcon: false },
firstName: {
field: 'firstName',
},
};
export default function ColumnContextMenuItems() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="getColumnMenuItems-example"
columnHeaderHeight={70}
columns={columns}
getColumnMenuItems={(items, { column }) => {
if (column.id === 'firstName') {
// you can adjust the default items for a specific column
items.splice(0, 0, {
key: 'firstName',
label: 'First name menu item',
onAction: () => {
console.log('Hey there!');
},
});
}
items.push(
{
key: 'hello',
label: 'Hello World',
onAction: () => {
alert('Hello World from column ' + column.id);
},
},
{
key: 'translate',
label: 'Translate',
menu: {
items: [
{
key: 'translateToEnglish',
label: 'English',
onAction: () => {
console.log('Translate to English');
},
},
{
key: 'translateToFrench',
label: 'French',
onAction: () => {
console.log('Translate to French');
},
},
],
},
},
);
return items;
}}
/>
>
);
}
```
As you can see in the demo above, you can use [`getColumnMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getColumnMenuItems) to return the default items (received as the first parameter to the function), or another totally different array. We chose to pass the default items to the function, so you can use them as a starting point and adjust them to your needs.
Each item in the array you return from [`getColumnMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getColumnMenuItems) should have a `key` and a `label` property. Additionally, you can specify an `onAction` function, which will be called when the user clicks the menu item.
It's also possible to create items with submenus. For this, specify a `menu` property in the item, with an `items` array. Each item in the `items` array should have a `key` and a `label` property, as you would expect.
```tsx {8} title="Menu_items_with_submenus"
function getColumnMenuItems(items, { column }) {
const items = [
{
key: 'translate',
label: 'Translate',
menu: {
items: [
{
key: 'translateToEnglish',
label: 'English',
onAction: () => {
console.log('Translate to English');
},
},
{
key: 'translateToFrench',
label: 'French',
onAction: () => {
console.log('Translate to French');
},
},
],
},
},
];
return items;
}
```
## Custom menu icon
To customize the menu icon, use the [`columns.renderMenuIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon) prop. This prop can be a boolean or a function that returns a `ReactNode`.
```tsx title="custom-menu-icon"
const columns = {
name: {
field: 'firstName',
renderMenuIcon: () =>
🌎
,
},
salary: {
field: 'salary',
renderMenuIcon: false,
},
};
```
For a custom menu icon 🌠 you don't have to hook up the `mousedown`/`click` in order to show or hide the menu - all this is done for you - just render your custom `ReactNode` and you're good to go.
---
# Column Order
> Change column order by drag-and-drop - drag columns around and reorder them live
Canonical page: https://infinite-table.com/docs/learn/columns/column-order
React `Infinite Table` allows columns to be reordered in the grid by drag-and-drop. Drag columns around (start dragging the colum header) to change their order and arrange them in the desired position.
Column ordering via drag & drop works by default.
You don't have to specify an initial column order or any other callback props to update the column order. The default behavior of the component is to initially display all columns that are provided in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object (in the iteration order of the object keys).
If using the [default uncontrolled column order](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnOrder) is not enough, try using the controlled [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) prop, which gives you full control over the order of the columns - in this case, you have to update the column order as a result of user interaction, by specifying [`onColumnOrderChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnOrderChange).
**Example: Column reordering via drag & drop with controlled `columnOrder`**
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';
export type Employee = {
id: number;
companyName: string;
companySize: string;
firstName: string;
lastName: string;
country: string;
countryCode: string;
city: string;
streetName: string;
streetNo: string;
department: string;
team: string;
salary: number;
age: number;
email: string;
};
export const columns: InfiniteTablePropColumns = {
firstName: {
field: 'firstName',
header: 'First Name',
},
country: {
field: 'country',
header: 'Country',
columnGroup: 'location',
},
city: {
field: 'city',
header: 'City',
columnGroup: 'address',
},
salary: {
field: 'salary',
type: 'number',
header: 'Salary',
},
department: {
field: 'department',
header: 'Department',
},
team: {
field: 'team',
header: 'Team',
},
company: { field: 'companyName', header: 'Company' },
companySize: {
field: 'companySize',
header: 'Company Size',
},
};
export default function App() {
const [columnOrder, setColumnOrder] = useState([
'firstName',
'country',
'team',
'company',
'department',
'companySize',
]);
return (
<>
Current column order:{' '}
{columnOrder.join(', ')}.
Drag column headers to reorder.
data={dataSource} primaryKey="id">
debugId="columnOrder-example"
columns={columns}
columnOrder={columnOrder}
onColumnOrderChange={setColumnOrder}
columnDefaultWidth={200}
/>
>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
.then((r) => r.json())
.then((data: Employee[]) => data);
};
```
Column order can also be used in order to limit/modify the visible columns. Specify a limited number of columns in the [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) array and only those columns will be displayed.
For more advanced control on visibility, see [`columnVisibility`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnVisibility).
The [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) array can contain any number of columns, even duplicate columns or random strings - the behavior is that any column ids which are not found in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object are ignored, while columns mentioned multiple times will be included multiple times, as indicated in the column order. Displaying the same column twice is a perfectly valid use case.
**Example: Advanced column order example**
In this example, [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) is used as a controlled property, also as a way of limiting the visible columns.
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';
export type Employee = {
id: number;
companyName: string;
companySize: string;
firstName: string;
lastName: string;
country: string;
countryCode: string;
city: string;
streetName: string;
streetNo: string;
department: string;
team: string;
salary: number;
age: number;
email: string;
};
export const columns: InfiniteTablePropColumns = {
firstName: {
field: 'firstName',
header: 'First Name',
},
country: {
field: 'country',
header: 'Country',
columnGroup: 'location',
},
city: {
field: 'city',
header: 'City',
columnGroup: 'address',
},
salary: {
field: 'salary',
type: 'number',
header: 'Salary',
},
department: {
field: 'department',
header: 'Department',
},
team: {
field: 'team',
header: 'Team',
},
company: { field: 'companyName', header: 'Company' },
companySize: {
field: 'companySize',
header: 'Company Size',
},
};
export default function App() {
const [columnOrder, setColumnOrder] = useState([
'firstName',
'country',
'team',
'company',
'firstName',
'not existing column',
'companySize',
]);
return (
<>
Current column order:{' '}
{JSON.stringify(columnOrder)}.
Note: if the column order contains columns that don't exist in the
`columns` definition, they will be skipped.
data={dataSource} primaryKey="id">
debugId="columnOrder-advanced-example"
columns={columns}
columnOrder={columnOrder}
onColumnOrderChange={setColumnOrder}
columnDefaultWidth={200}
/>
>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
.then((r) => r.json())
.then((data: Employee[]) => data);
};
```
The [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) prop can either be an array of strings (column ids) or the boolean `true`. When `true`, all columns present in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object will be displayed, in the iteration order of the object keys - in the example above, try clicking the `"Click to reset column order"` button.
For all of the above examples, [`columnVisibility`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnVisibility) will also be taken into account, as it is the last source of truth for the visibility of a column.
Using [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) in combination with [`columnVisibility`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnVisibility) is very powerful - for example, you can have a specific column order even for columns which are not visible at a certain moment, so when they will be made visible, you'll know exactly where they will be displayed.
---
# Column Rendering
> Customize column rendering for Infinite Table DataGrid to match your app and use custom components. Column styling and formatting, conditional rendering...
Canonical page: https://infinite-table.com/docs/learn/columns/column-rendering
Columns render the [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) value of the data they are bound to. This is the default behavior, which can be customized in a number of ways that we're exploring below.
If you want to explicitly use the TypeScript type definition for columns, import the `InfiniteTableColumn` type
```
import { InfiniteTableColumn } from '@infinite-table/infinite-react'
```
Note that it's a generic type, so when you use it, you have to bind it to your `DATA_TYPE` (the type of your data object).
When using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have local/transient state.
This is important because `InfiniteTable` uses virtualization heavily, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).
## Change the value using `valueGetter`
The simplest way to change what's being rendered in a column is to use the `valueGetter` prop and return a new value for the column.
```tsx
const nameColumn: InfiniteTableColumn = {
header: 'Employee Name',
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
};
```
The [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) prop is a function that takes a single argument - an object with `data` and `field` properties.
Note that the [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) is only called for non-group rows, so the `data` property is of type `DATA_TYPE`.
**Example: Column with custom valueGetter**
```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
id: { field: 'id', defaultWidth: 80 },
name: {
header: 'Full Name',
valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
},
preferredLanguage: { field: 'preferredLanguage' },
stack: { field: 'stack' },
};
export default function ColumnValueGetterExample() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="column-valueGetter-example"
columns={columns}
columnDefaultWidth={200}
/>
>
);
}
```
The column value getter should not return JSX or other markup, because the value return by [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) will be used when the column is sorted (when sorting is done client-side and not remotely). For more in-depth information on sorting see [the column sorting page](./column-sorting).
## Use [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) to display custom content
The next step in customizing the rendering for a column is to use the [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) or the [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) props. In those functions, you have access to more information than in the [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function. For example, you have access to the current value of `groupBy` and `pivotBy` props.
[renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) can return any value that React can render.
The [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) functions are called with an object that has the following properties:
- `data` - the data object (of type `DATA_TYPE | Partial | null`) for the row.
- `rowInfo` - very useful information about the current row:
- `rowInfo.collapsed` - if the row is collased or not.
- `rowInfo.groupBy` - the current group by for the row
- `rowInfo.indexInAll` - the index of the row in the whole data set
- `rowInfo.indexInGroup` - the index of the row in the current group
- `rowInfo.value` - the value (only for group rows) that will be rendered by default in group column cells.
- ... there are other useful properties that we'll document in the near future
- `column` - the current column being rendered
- `columnsMap` - the `Map` of columns available to the table. Note these might not be all visible. The keys in this map will be column ids.
- `fieldsToColumn` a `Map` that links `DataSource` fields to columns. Columns bound to fields (so with [`columns.field`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) specified) will be included in this `Map`.
- `api` - A reference to the [Infinite Table API](https://infinite-table.com/docs/reference/api/index.md) object.
[`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) is the last function called in the rendering pipeline for a column cell, while [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) is called before render, towards the beginning of the [rendering pipeline (read more about this below)](#rendering-pipeline).
Avoid over-writing [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) for special columns (like group columns) unless you know what you're doing. Special columns use the `render` function to render additional content inside the column (eg: collapse/expand tool for group rows). The [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) function allows you to override this additional content. So if you specify this function, it's up to you to render whatever content, including the collapse/expand tool.
However, there are easier ways to override the collapse/expand group icon, like using [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon).
Inside the [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) functions (and other rendering functions), you can use the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) hook to retrieve the same params that are passed to the render functions.
This is especially useful when inside those functions you render a custom component that needs access to the same information.
```tsx
type Developer = { country: string; name: string; id: string };
const CountryInfo = () => {
const { data, rowInfo, value } = useInfiniteColumnCell();
return
);
};
const nameColumn: InfiniteTableColumn = {
header: 'Name',
field: 'firstName',
components: {
ColumnCell,
HeaderCell,
},
};
```
When using custom components, make sure you get `domRef` from the corresponding hook ([`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) for column cells and [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) for header cells) and pass it on to the final `JSX.Element` that is the DOM root of the component.
```tsx
// inside a component specified in column.components.ColumnCell
const { domRef } = useInfiniteColumnCell();
return
...
;
```
Also you have to make sure you spread all other `props` you receive in the component, as they are `HTMLProps` that need to end-up in the DOM (eg: `className` for theming and default styles, etc).
Both [components.ColumnCell](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell) and [components.HeaderCell](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.HeaderCell) need to be declared with `props` being of type `HTMLProps`.
**Example: Custom components**
```tsx
import {
InfiniteTable,
DataSource,
useInfiniteColumnCell,
useInfiniteHeaderCell,
InfiniteTablePropColumnTypes,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const DefaultHeaderComponent: React.FunctionComponent<
React.HTMLProps
> = (props) => {
const { column, domRef, columnSortInfo } = useInfiniteHeaderCell();
const style = {
...props.style,
border: '1px solid #fefefe',
};
let sortTool = '';
switch (columnSortInfo?.dir) {
case undefined:
sortTool = '👉';
break;
case 1:
sortTool = '👇';
break;
case -1:
sortTool = '☝🏽';
break;
}
return (
{/* here you would usually have: */}
{/* {props.children} {sortTool} */}
{/* but in this case we want to override the default sort tool as well (which is part of props.children) */}
{column.field} {sortTool}
);
};
const columnTypes: InfiniteTablePropColumnTypes = {
default: {
// override all columns to use these components
components: {
HeaderCell: DefaultHeaderComponent,
},
},
};
const columns: InfiniteTablePropColumns = {
id: { field: 'id', defaultWidth: 80 },
stack: {
field: 'stack',
renderValue: ({ data }) => 'Stack: ' + data?.stack,
components: {
HeaderCell: DefaultHeaderComponent,
ColumnCell: StackComponent,
},
},
firstName: {
field: 'firstName',
},
preferredLanguage: {
field: 'preferredLanguage',
},
};
export default function ColumnValueGetterExample() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="column-components-example"
columns={columns}
columnTypes={columnTypes}
/>
>
);
}
```
If you're using the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) hook inside the [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) or [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) functions (and not as part of a custom component in [`columns.components.ColumnCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell)), you don't need to pass on the `domRef` to the root of the DOM you're rendering (same is true if you're using [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) inside the [`columns.header`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) function).
If the above [`columns.components`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components) is still not enough, read about the rendering pipeline below.
## Rendering pipeline
The rendering pipeline for columns is a series of functions defined on the column that are called while rendering.
All the functions that have the word `render` in their name will be called with an object that has a `renderBag` property, which contains values that will be rendered.
The default [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) function (the last one in the pipeline) ends up rendering a few things:
- a `value` - generally comes from the [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) the column is bound to
- a `groupIcon` - for group columns
- a `selectionCheckBox` - for columns that have [`columns.renderSelectionCheckBox`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox) defined (combined with row selection)
When the rendering process starts for a column cell, all the above end up in the `renderBag` object.
### Rendering pipeline - `renderBag.value`
As already mentioned, the `value` defaults to the value of the column [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) for the current row.
If the column is not bound to a field, you can define a [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter). The [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) only has access to `{data, field?}` in order to compute a value and return it.
After the [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) is called, the [valueFormatter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) is next in the rendering pipeline.
This is called with more details about the current cell
```tsx
const column: InfiniteTableColumn = {
// the valueGetter can be useful when rows are nested objects
// or you want to compose multiple values from the row
valueGetter: ({ data }) => {
return data.person.salary * 10;
},
valueFormatter: ({
value,
isGroupRow,
data,
field,
rowInfo,
rowSelected,
rowActive,
}) => {
// the value here is what the `valueFormatter` returned
return `USD ${value}`;
},
};
```
After [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) and [valueFormatter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) are called, the resulting value is the actual value used for the cell. This value will also be assigned to `renderBag.value`
When [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) are called by `InfiniteTable`, both `value` and `renderBag` will be available as properties to the arguments object.
```tsx {3,12}
const column: InfiniteTableColumn = {
valueGetter: () => 'world',
renderValue: ({ value, renderBag, rowInfo }) => {
// at this stage, `value` is 'world' and `renderBag.value` has the same value, 'world'
return {value};
},
render: ({ value, renderBag, rowInfo }) => {
// at this stage `value` is 'world'
// but `renderBag.value` is world, as this was the value returned by `renderValue`
return
Hello {renderBag.value}!
;
},
};
```
After the [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) function is called, the following are also called (if available):
- [renderGroupValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue) - for group rows
- [renderLeafValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderLeafValue) - for leaf rows
You can think of them as an equivalent to [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue), but narrowed down to group/non-group rows.
Inside those functions, the `renderBag.value` refers to the value returned by the [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) function.
### Rendering pipeline - `renderBag.groupIcon`
In a similar way to `renderBag.value`, the `renderBag.groupIcon` is also piped through to the [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) function.
```tsx {2,9}
const column: InfiniteTableColumn = {
renderGroupIcon: ({ renderBag, toggleGroupRow }) => {
return <> [ {renderBag.groupIcon} ] >;
},
render: ({ renderBag }) => {
return (
<>
{/* use the groupIcon from the renderBag */}
{renderBag.groupIcon}
{renderBag.value}
>
);
},
};
```
Inside [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon), you have access to `renderBag.groupIcon`, which is basically the default group icon - so you can use that if you want, and build on that.
Also inside [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon), you have access to `toggleGroupRow` so you can properly hook the collapse/expand behaviour to your custom group icon.
### Rendering pipeline - `renderBag.selectionCheckBox`
Like with the previous properties of `renderBag`, you can customize the `selectionCheckBox` (used when multiple selection is configured) to be piped-through - for columns that specify [`columns.renderSelectionCheckBox`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox).
```tsx {2,25}
const column: InfiniteTableColumn = {
renderSelectionCheckBox: ({
renderBag,
rowSelected,
isGroupRow,
toggleCurrentRowSelection,
toggleCurrentGroupRowSelection,
}) => {
const toggle = isGroupRow
? toggleCurrentGroupRowSelection
: toggleCurrentRowSelection;
// you could return renderBag.groupIcon to have the default icon
const selection =
rowSelected === null
? '-' // we're in a group row with indeterminate state if rowSelected === null
: rowSelected
? 'x'
: 'o';
return
[ {selection} ]
;
},
render: ({ renderBag }) => {
return (
<>
{/* use the selectionCheckBox from the renderBag */}
{renderBag.selectionCheckBox}
{renderBag.groupIcon}
{renderBag.value}
>
);
},
};
```
To recap, here is the full list of the functions in the rendering pipeline, in order of invocation:
1. [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) - doesn't have access to `renderBag`
2. [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) - doesn't have access to `renderBag`
3. [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon) - can use all properties in `renderBag`
4. [`columns.renderSelectionCheckBox`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSelectionCheckBox) - can use all properties in `renderBag`
5. [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) - can use all properties in `renderBag`
6. [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue) - can use all properties in `renderBag`
7. [`columns.renderLeafValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderLeafValue) - can use all properties in `renderBag`
8. [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) - can use all properties in `renderBag`
Additionally, the [`columns.components.ColumnCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell) custom component does have access to the `renderBag` via [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell)
---
# Column Sorting
> Configure column sorting with single and multiple sorting and custom sort functions. Both client-side and server-side sorting is supported
Canonical page: https://infinite-table.com/docs/learn/columns/column-sorting
Docs coming soon
---
# Column Types
> Column types are blueprints for generalizing column configuration and code reuse.
Canonical page: https://infinite-table.com/docs/learn/columns/column-types
Column types allow you to specify common properties for multiple columns easily. Things like [minWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.minWidth), [maxWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.maxWidth), [defaultFlex](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultFlex) and [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.header) are all available. For a full list, see [columnTypes](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes) reference.
You specify the type of a column via the [column.type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) property:
```tsx
type Person = {
name: string;
dob: string;
age: number;
}
const columns = {
age: {
field: 'age',
type: 'custom',
},
date: {
field: 'dob',
// will be type default
},
name: {
field: 'name',
// will have both of those types
type: ['default', 'custom']
}
}
const columnTypes = {
default: {
width: 200
},
custom: {
align: 'center'
}
}
```
Properties defined in a column have precedence over the properties defined in the [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes).
Also, if a column has no [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) specified, it will default to the `default` type. If you don't want a column to have the `default` type, use [column.type=null](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) or [column.type=[]](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type)
The column [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) property can be an array - in this case, types are applied in the order they are specified, later types overriding properties of earlier ones. If the `default` type is not specified in the array, it will not be applied to the column - if you want to apply it as well, use [type=['default', 'any', 'other', 'types', 'after']](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type)
## Column Type properties order and precedence
When a column has multiple column types, they are applied in order, from left to right, with later types overriding properties of earlier ones - think of the behavior as very similar to `Object.assign`.
Assume a column has the following types:
```tsx
const columns = {
salary: {
type: ['number', 'currency'],
},
};
const numberFormatter = new Intl.NumberFormat();
const columnTypes = {
number: {
renderValue: ({ value }) => numberFormatter.format(value), // makes 12345 render as 12,345
},
currency: {
renderValue: ({ value }) => `USD: ${value}`, // makes 12345 render as USD: 12345
},
};
```
Although the `salary` column has both the `number` and `currency` types, and both those types have the `renderValue` property defined, only the `currency` `renderValue` function will be called. In other words, the rendering is not piped from one column type to the next. This is applied for all properties, like `render`, `style`, etc.
The `renderValue` function (and other similar functions) has access to the `column` object, so you can manually access all the column types.
---
# Column Sizing
Canonical page: https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size
Columns are a core concept for `Infinite Table` and sizing columns is an important topic to master. Here is a summary of how columns can be sized:
- fixed-sized columns can be specified via [`columns.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth)
- flexible columns need [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex)
- [`columns.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.minWidth) specifies the minimum size for a column
- [`columns.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.maxWidth) is for the maximum width a column can take
- default values are available for all of the above:
- [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth) gives all columns (that are otherwise unconfigured) a default size
- [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) specifies the minimum width for all columns (that don't have one)
- [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) specifies the maximum width for all columns (that don't have one)
For fine-grained controlled-behavior on column sizing, use the controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) prop (for uncontrolled variant, see [`defaultColumnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing)). If you want to get updates to columns changing size as a result of user interaction, use [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange).
Use [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth) to configure the default column width. If a column is not sized otherwise, this will be applied. The default value for [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth) is `200` (pixels).
For setting a minimum and maximum width for all columns, use [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) (defaults to `30`) and [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) (defaults to `2000`) respectively.
## Understanding default column sizing
The easiest way to get started and specify a sizing behavior for columns is to use [column.defaultWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth), [column.defaultFlex](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex) and/or [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth) (including related pros for specifying limits, like [column.minWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.minWidth), [column.maxWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.maxWidth) and [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) / [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth)).
Those properties have `default` in their name because after the initial rendering of a column, you can't change its size by updating those values - more technically, [column.defaultWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth) and [column.defaultFlex](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex) are uncontrolled props.
We suggest you use those to get started and if you don't have care about responding to the user changing the widths of those columns via drag&drop. As long as you're not using [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) to be notified of column size changes, you're probably good with those.
## Controlled column sizing
However, once you start using [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) and want to have full control of column sizing (maybe you want to restore it later to the state the user had it when the app was closed), you probably want to use controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing).
The [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) prop is an object of column ids to column sizing objects. Those sizing objects can have the following properties:
- [flex](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.flex) - use this for flexible columns. Behaves like the flex CSS property.
- [width](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.width) - use this for fixed sized columns
- [minWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.minWidth) - specifies the minimum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
- [maxWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.maxWidth) - specifies the maximum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
If a column is not specified in the [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) prop (or its uncontrolled variant), or sized otherwise (eg: via the column type), it will have a fixed size, defaulting to [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth) (which also defaults to `200` if no value is passed in). You can also specify a [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) and [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) - those will be applied for all columns (namely for those that dont explicitly specify other min/max widths).
```tsx
const columnSizing: InfiniteTablePropColumnSizing = {
country: {
flex: 1,
// minWidth is optional
minWidth: 200,
},
city: {
width: 400,
// and so is maxWidth
maxWidth: 500,
},
salary: {
flex: 3,
},
};
// any column not specified in the columnSizing (or defaultColumnSizing) prop
// will have fixed width (defaulting to `columnDefaultWidth`, which in turn defaults to 200px)
```
You might find specifying the column size outside the column object to be a bit verbose to start with, but it will be easier to manage in many cases and is much more flexible. For example, when the user resizes a column via drag & drop and you want to persist the new column sizes, you don't have to update the whole `columns` object but instead update [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) alone.
The same principle is true for [`columnPinning`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnPinning) and other column-level props.
The `columnSizing` prop also has an uncontrolled version, namely [`defaultColumnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing).
## Using flexible column sizing
The way flex sizing is implemented is similar to how CSS flexbox algorithm works. Explore this section to find out more details.
Imagine you have `1000px` of space available to the viewport of `InfiniteTable` and you have 3 columns:
- a fixed column `100px` wide - name it col `A`
- a fixed column `300px` wide - name it col `B`
- a flexible column with `flex: 1` - name it col `F1`
- a flexible column with `flex: 2` - name it col `F2`
The space remaining for the flexible columns is `1000px - 400px = 600px` and the sum of all flex values is `3`, that means each `flex` unit will be `600px / 3 = 200px`.
This means columns will have the following sizes:
- col `A` will be `100px`
- col `B` will be `300px`
- col `F1` will be `200px` ( so a flex unit)
- col `F2` will be `400px` ( so the equivalent of `2` flex units)
If the browser changes the layout of the component, so `InfiniteTable` has only `700px` available, then a flex unit would be `(700px - 400px) / 3 = 100px`.
This means columns will have the following sizes:
- col `A` will be `100px`
- col `B` will be `300px`
- col `F1` will be `100px` ( so a flex unit)
- col `F2` will be `200px` ( so the equivalent of `2` flex units)
The flexbox algorithm also uses [`viewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#viewportReservedWidth) to determine the width of the viewport to use for sizing columns - you can use [viewportReservedWidth=100](https://infinite-table.com/docs/reference/infinite-table-props.md#viewportReservedWidth) to always have a `100px` reserved area that won't be used for flexing columns.
**Example: Using viewportReservedWidth to reserve whitespace when you have flexible columns**
This example has a `viewportReservedWidth` of `50px`.
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumnSizing,
InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';
export const columns: Record> = {
firstName: {
field: 'firstName',
header: 'First Name',
},
country: {
field: 'country',
header: 'Country',
},
city: {
field: 'city',
header: 'City',
},
salary: {
field: 'salary',
type: 'number',
header: 'Salary',
},
};
const defaultColumnSizing: InfiniteTablePropColumnSizing = {
country: { flex: 1 },
city: { flex: 1 },
salary: { flex: 2 },
};
export default function App() {
const [viewportReservedWidth, setViewportReservedWidth] = useState(0);
return (
<>
Current viewport reserved width: {viewportReservedWidth}px.
data={dataSource} primaryKey="id">
debugId="columnSizing-example"
columns={columns}
columnDefaultWidth={50}
columnSizing={columnSizing}
onColumnSizingChange={setColumnSizing}
viewportReservedWidth={viewportReservedWidth}
onViewportReservedWidthChange={setViewportReservedWidth}
/>
>
);
}
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
.then((r) => r.json())
.then((data: Employee[]) => data);
};
export type Employee = {
id: number;
companyName: string;
companySize: string;
firstName: string;
lastName: string;
country: string;
countryCode: string;
city: string;
streetName: string;
streetNo: string;
department: string;
team: string;
salary: number;
age: number;
email: string;
};
```
You might find [`viewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#viewportReservedWidth) useful for advanced configuration when you have flexible columns.
When he user is performing a column resize (via drag & drop), [`onViewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidth) is called when the resize is finished (not the case for resizing with the **SHIFT** key pressed, when adjacent columns share the space between them).
You can also size (generated) group columns by using their [column.id](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.id) property.
For [groupRenderStrategy="multi-column"](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy), if no `id` is specified in the group column configuration, each column will have a generated id like this: `"group-by-${field}"`.
For [groupRenderStrategy="single-column"](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy), if no `id` is specified in the [`groupColumn`](https://infinite-table.com/docs/reference/infinite-table-props.md#groupColumn) it will default to: `"group-by"`.
## Resizing columns via drag & drop
Columns are user-resizable via drag & drop. If you don't want a column to be resizable, specify [column.resizable=false](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.resizable)
By default, all columns are resizable since [`resizableColumns`](https://infinite-table.com/docs/reference/infinite-table-props.md#resizableColumns) defaults to `true`. The [`resizableColumns`](https://infinite-table.com/docs/reference/infinite-table-props.md#resizableColumns) prop controls the behavior for all columns that don't explicitly specify their [column.resizable](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.resizable) property.
When initially rendered, columns are displayed with their [`columns.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth) (you can also use [`columnDefaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultWidth)) or [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex). Flexible columns take up available space taking into account their flex value, as detailed above.
When the user is resizing columns (or column groups), the effect is seen in real-time, so it's very easy to adjust the columns to desired widths. After the user drops the resize handle to the desired position, [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) is being called, to allow the developer to react to column sizing changes. Also [`onViewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidth) is called as well when the resize is finished (not the case for resizing with the **SHIFT** key pressed, when adjacent columns share the space between them).
When flexible columns are resized, they are kept flexible even after the resize. Note however that their flex values will be different to the original flex values and will reflect the new proportions each flex column is taking up at the moment of the resize.
More exactly, the new flex values will be the actual pixel widths. As an example, say there are 2 flex columns, first one with flex `1` and second one with flex `3` and they have an available space of `800px`.
```ts
const columns = {
first: { flex: 1, field: 'one' },
second: { flex: 2, field: 'two' },
};
```
Initially they will occupy `200px` and `600px` respectively. If the user resizes them to be of equal size, [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) will be called with an object like
```ts
{
first: { flex: 400 },
second: {flex: 400 }
}
```
since those are the actual widths measured from the DOM. This works out well, even if the available space of the table grows, as the proportions will be the same.
### Resize Restrictions
When resizing, the user needs to drag the resize handle to adjust the columns to new sizes. While doing so, the resize handle has a (green) color to indicate everything is okay. However, when restrictions are hit (either column [min](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.minWidth) or [max](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.maxWidth) widths), the resize handle turns red to indicate further resizing is not possible.
### Sharing space on resize
By default when resizing a specific column, the following columns are pushed to the right (when making the column wider) or moved to the left (when making the column narrower).
For sharing space between resizable columns when resizing, the user needs to **hold the SHIFT key** when grabbing the resize handle. When the handle is dropped and the resize confirmed, [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) is called, but [`onViewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidth) is not called for this scenario, since the reserved width is preserved.
### Resizing column groups
Just as columns are being resized, it is also possible to resize column groups. For this, the user needs to hover over the right border of the column group and start dragging the resize handle.
For multi-level column groups, it's possible to resize any of them. Just grab the handle from the desired group and start dragging. The handle height will indicate which column group is being resized.
If a column group has at least one resizable column, it can be resized.
When resizing, the space is shared proportionally betweem all resizable columns in the group.
Once a min/max limit has been reached for a certain column in the group, the column respects the limit and the other columns keep resizing as usual. When the min/max limit has been reached for all columns in the group, the resize handle turns red to indicate further resizing is no longer possible.
**Example: Resizing column groups**
Try resizing the `Finance` and `Regional Info` column groups.
The columns in the `Finance` group can be resized an extra `30px` (they have a `maxWidth` of `130px`).
```tsx
import {
InfiniteTable,
DataSource,
InfiniteTableColumnGroup,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
currency: {
field: 'currency',
columnGroup: 'finance',
maxWidth: 130,
},
salary: {
field: 'salary',
columnGroup: 'finance',
maxWidth: 130,
},
country: {
field: 'country',
columnGroup: 'regionalInfo',
maxWidth: 400,
},
preferredLanguage: {
field: 'preferredLanguage',
columnGroup: 'regionalInfo',
},
id: { field: 'id', defaultWidth: 80 },
firstName: {
field: 'firstName',
},
stack: {
field: 'stack',
},
};
const columnGrous: Record = {
regionalInfo: {
header: 'Regional Info',
},
finance: {
header: 'Finance',
columnGroup: 'regionalInfo',
},
};
export default function ColumnValueGetterExample() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="column-groups-example"
columnGroups={columnGrous}
columns={columns}
columnDefaultWidth={100}
/>
>
);
}
```
### Customizing the resize handle colors
It's possible to customize the resize handle colors and width.
For adjusting the handle colors, use the following CSS variables:
- `--infinite-resize-handle-hover-background` - the color of the resize handle when it's in a `green`/all good state.
- `--infinite-resize-handle-constrained-hover-background` - the color of the resize handle when it has reached a min/max constraint.
You can also adjust the width of the resize handle:
- `--infinite-resize-handle-width` - the width of the `green`/`red` column resize handle. Defaults to `2px`
- `--infinite-resize-handle-active-area-width` - the width of the area you can hover over in order to grab the resize handle. Defaults to `20px`. The purpose of this active area is to make it easier to grab the resize handle.
## Auto-sizing columns
For sizing columns to the width of their content, you can use [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey) to declaratively auto-size columns:
- when [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey) is a `string` or `number` and the value of the prop is changed, all columns will be auto-sized.
- when [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey) is an object, it needs to have a `key` property (of type `string` or `number`), so whenever the `key` changes, the columns will be auto-sized. Specifying an object for [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey) gives you more control over which columns are auto-sized and if the size measurements include the header or not.
When an object is used, the following properties are available:
- `key` - mandatory property, which, when changed, triggers the update
- `includeHeader` - optional boolean, - decides whether the header will be included in the auto-sizing calculations. If not specified, `true` is assumed.
- `columnsToSkip` - a list of column ids to skip from auto-sizing. If this is used, all columns except those in the list will be auto-sized.
- `columnsToResize` - the list of column ids to include in auto-sizing. If this is used, only columns in the list will be auto-sized.
**Example: Auto-sizing columns**
```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
id: { field: 'id' },
firstName: { field: 'firstName' },
preferredLanguage: { field: 'preferredLanguage' },
stack: { field: 'stack' },
country: { field: 'country' },
age: { field: 'age', type: 'number' },
salary: { field: 'salary', type: 'number' },
currency: { field: 'currency', type: 'number' },
};
export default function GroupByExample() {
const [key, setKey] = React.useState(0);
const [includeHeader, setIncludeHeader] = React.useState(false);
const autoSizeColumnsKey = React.useMemo(() => {
return {
includeHeader,
key,
};
}, [key, includeHeader]);
return (
<>
primaryKey="id" data={dataSource}>
debugId="autoSizeColumnsKey-example"
autoSizeColumnsKey={autoSizeColumnsKey}
columns={columns}
columnDefaultWidth={200}
/>
>
);
}
```
---
# Common Issues
> Avoid common pitfalls and issues when using the component. Learn how to use it properly to perform smooth and avoid jank.
Canonical page: https://infinite-table.com/docs/learn/common-issues/
As people have started using `` we've noticed a few issues keep popping up.
While we're trying to refine our API to be easier to use and understand, developers using the component still need to be aware of some design decisions and conventions used in the component.
## Issue: Performance degradation because props are new on every render
Passing new props on every render to the `` component or to the `` component can be a performance bottleneck:
```ts
```
Instead pass the **same** reference when things do change - stored in state or any other place:
```ts
const [groupBy, setGroupBy] = useState([{ field: 'country' }]);
;
```
When in dev mode, you can set `localStorage.debug = "*"` in your localstorage to see potential issues logged to the console.
For example, you might see:
`InfiniteTable:rerender Triggered by new values for the following props +1s columns`
## Issue: State inside custom components rendered in cells is lost while scrolling
When using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have any local or transient state.
This is important because `InfiniteTable` makes heavy use of virtualization, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).
---
# Compare React DataGrids
> Honest comparison of Infinite Table against AG Grid, TanStack Table, and MUI X Data Grid. Find the right React data grid for your project.
Canonical page: https://infinite-table.com/docs/learn/compare/
Infinite Table was built for React from the ground up. Columns are props. Sorting, grouping, and filtering are controlled or uncontrolled values — the same pattern you use for an ``. Cell renderers are plain JSX. State lives in React. When you change a prop, the grid re-renders.
Other excellent data grids take different approaches — multi-framework support, headless logic libraries, deep Material UI integration — and those approaches are the right choice for many teams. This section compares Infinite Table with three popular alternatives so you can decide which fits your project.
## Comparisons
- [Infinite Table vs AG Grid](https://infinite-table.com/docs/learn/compare/ag-grid.md) — The most established enterprise data grid. Multi-framework, huge feature set. AG Grid's breadth is unmatched; Infinite Table focuses on a small, composable, React-declarative API surface.
- [Infinite Table vs TanStack Table](https://infinite-table.com/docs/learn/compare/tanstack-table.md) — A headless, MIT-licensed logic library. Total rendering control — you build the UI, the virtualization, and the keyboard navigation. Infinite Table ships those built-in.
- [Infinite Table vs MUI X Data Grid](https://infinite-table.com/docs/learn/compare/mui-x-data-grid.md) — A rendered data grid from the Material UI ecosystem. Excellent MUI integration. Infinite Table is design-system agnostic and includes grouping and pivoting without a Premium tier.
## Quick Comparison
| | Infinite Table | AG Grid | TanStack Table | MUI X Data Grid |
|---|---|---|---|---|
| **Built for React** | Yes — from the ground up | Multi-framework (JS, Angular, Vue, React) | Headless — multi-framework hooks | Yes — React only |
| **API style** | Small, composable props (controlled + uncontrolled) | Large, comprehensive configuration surface | Headless hooks, you provide all JSX | Declarative props, controlled + uncontrolled |
| **Cell renderers** | JSX components | AG Grid component interface (React supported) | You build all rendering | JSX components |
| **Frameworks** | React | React, Angular, Vue, JS | All Most Popular | React |
| **Virtualization** | Row + column | Row + column | BYO (separate package) | Row (column in Pro+) |
| **Grouping** | Included (free) | Enterprise license | Logic only (free) | Premium plan ($599/dev/yr) |
| **Pivoting** | Included (free) | Enterprise license | Logic only (free) | Premium plan ($599/dev/yr) |
| **Tree data** | Included (free) | Enterprise license | Logic only (free) | Pro plan ($299/dev/yr) |
| **License** | Free with footer; paid removes footer | Community MIT; Enterprise proprietary | MIT | Community MIT; Pro/Premium/Enterprise proprietary |
| **Paid license** | [$395/dev/year](https://infinite-table.com/pricing) | [~$999/dev/year](https://www.ag-grid.com/license-pricing) | Free | [$299/dev/year (Pro)](https://mui.com/pricing/) |
Feature availability is based on each product's official documentation as of mid-2026. Always verify on the vendor's site before making a purchasing decision.
## How to decide
**Pick Infinite Table** if you want a data grid that feels like a native React component — a small, composable API of declarative props, controlled state, and JSX renderers — with grouping, pivoting, and aggregations included out of the box, no enterprise license required.
**Pick AG Grid** if you need multi-framework support (Angular, Vue, and React under one grid), the widest possible feature surface (charting, clipboard, server-side row model), or your team already knows AG Grid from other projects and values the breadth it provides.
**Pick TanStack Table** if you want total control over rendering and are prepared to build your own UI layer, virtualization, keyboard navigation, and accessibility. Best for design-system component libraries or lightweight tables that don't need complex built-in features.
**Pick MUI X Data Grid** if your application is already built on Material UI and you value automatic theme integration with MUI's design system, or you need the broader MUI X component suite (date pickers, charts, tree view) under a single license.
How Infinite Table's React-declarative surface compares with AG Grid's multi-framework approach.
A rendered, virtualized DataGrid versus a headless table library you assemble yourself.
What ships in Infinite Table's free build versus MUI X Community, Pro, and Premium.
Grouping, aggregations, and pivoting — included in every Infinite Table build.
## Help us keep these comparisons up-to-date
These pages are our reading of each product's public documentation and pricing as of mid-2026. We want them to stay accurate. If you work on AG Grid, TanStack Table, or MUI X — or you've spotted something that's wrong, outdated, or missing context — please tell us. We will update the page.
- [File a correction issue](https://github.com/infinite-table/infinite-react/issues/new?template=compare_page_correction.md&title=Compare%20page%20correction%3A%20)
- [Edit the source on GitHub](https://github.com/infinite-table/infinite-react/tree/master/www/content/docs/learn/compare) and open a pull request
- Email [admin@infinite-table.com](mailto:admin@infinite-table.com?subject=Compare%20page%20correction) with the page URL and what should change
---
# Infinite Table vs AG Grid
> A detailed comparison of Infinite Table and AG Grid for React. Two different approaches to building data grids — and when each one is the right choice.
Canonical page: https://infinite-table.com/docs/learn/compare/ag-grid
[AG Grid](https://www.ag-grid.com/) is the most established commercial data grid on the market, used across React, Angular, Vue, and plain JavaScript. It has a massive feature surface and a large community. The AG Grid team has been shipping for over a decade and the result is an impressively comprehensive product.
The core difference is not price. It's how each grid relates to React.
## Two different approaches to React
AG Grid supports four frameworks from a single codebase. That multi-framework architecture is a genuine strength — it means your organisation can standardise on one grid across Angular, Vue, and React projects. The trade-off is that AG Grid's core is framework-agnostic: state and rendering live inside the grid engine, with a React adapter layer on top. Configuration goes through a `gridOptions` object, and many operations use imperative API calls like `api.setColumnDefs()` or `api.refreshCells()`.
Infinite Table is built for React, and that choice shapes the API. Columns, sorting, grouping, and filtering are React props — controlled or uncontrolled, like any React form component. Cell renderers are plain JSX. The grid participates in React's component tree, re-rendering when props change.
Neither approach is wrong. They reflect different design priorities: AG Grid optimises for framework reach and breadth; Infinite Table optimises for feeling native to React.
## What this looks like in code
With AG Grid, updating columns typically goes through the API:
```tsx
// AG Grid: update columns via the imperative API
const onButtonClick = () => {
gridRef.current.api.setColumnDefs(newColumnDefs);
gridRef.current.api.refreshCells({ force: true });
};
```
With Infinite Table, the same operation is a state change:
```tsx
// Infinite Table: update columns via React state
const [columns, setColumns] = useState(initialColumns);
const onButtonClick = () => {
setColumns(newColumns); // grid re-renders automatically
};
primaryKey="id" data={dataSource}>
columns={columns} />
```
The same `value` / `onChange` pattern you use for a React `` works for the entire grid.
## API surface: breadth vs composability
AG Grid covers a huge number of enterprise use cases, and its API reflects that breadth. The [`GridOptions` interface](https://www.ag-grid.com/react-data-grid/grid-options/) spans hundreds of props across 25+ categories — from row grouping and pivoting to charting, clipboard, and server-side row models. For teams that need that coverage, it's all there.
That breadth naturally comes with complexity. Over a decade of multi-framework development, the API has grown to include multiple ways to configure the same behaviour — for example, overlays can be customised through `overlayComponent`, or through the older `loadingOverlayComponent` and `noRowsOverlayComponent`, or through template strings, all of which still work. Row grouping alone involves roughly 20 grid-level props. These aren't flaws — they're the result of supporting many years of backwards compatibility across four frameworks, and teams that rely on those options are glad they exist.
Infinite Table makes a different trade-off: keep the API surface small and composable.
- **Function props as building blocks.** The [`groupColumn`](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows.md) prop can be a column object (single group column) or a function (called for each generated column). One prop, two behaviours, composed through the same mechanism.
- **Controlled and uncontrolled variants.** Want to manage sorting yourself? Pass `sortInfo` (controlled). Want the grid to handle it? Pass `defaultSortInfo` (uncontrolled). The same pattern as `value` vs `defaultValue` on a React ``.
- **Fewer props to coordinate.** Infinite Table ships grouping, pivoting, and aggregations — but with a smaller configuration surface. The bet is that fewer, more composable props are easier to learn and reason about for React teams.
These are genuinely different philosophies. AG Grid's large surface means there's usually a dedicated prop for any specific requirement. Infinite Table's smaller surface means you compose general-purpose building blocks to get there.
## Architecture
| | Infinite Table | AG Grid |
|---|---|---|
| **Built for** | React | Multi-framework (JS, Angular, Vue, React) |
| **React integration** | Native — renders through React's reconciler | Framework-agnostic core with React adapter |
| **API style** | Declarative props, controlled + uncontrolled | Comprehensive configuration object, imperative API |
| **Cell renderers** | Plain JSX components | AG Grid component interface (React components supported) |
| **State management** | Lives in React (useState, context, external stores) | Lives inside the grid; synced to React via callbacks |
| **TypeScript** | Written in TypeScript, first-class types | Written in TypeScript, first-class types |
| **Virtualization** | Row + column | Row + column |
## Feature Comparison
| Feature | Infinite Table (free) | AG Grid Community (free) | AG Grid Enterprise (paid) |
|---|---|---|---|
| Sorting (single + multi) | ✅ | ✅ | ✅ |
| Column filtering | ✅ | ✅ | ✅ |
| Column resizing | ✅ | ✅ | ✅ |
| Column reordering | ✅ | ✅ | ✅ |
| Column pinning | ✅ | ✅ | ✅ |
| Column grouping (headers) | ✅ | ✅ | ✅ |
| Row grouping | ✅ | 🔴 | ✅ |
| Aggregations | ✅ | 🔴 | ✅ |
| Pivoting | ✅ | 🔴 | ✅ |
| Tree data | ✅ | 🔴 | ✅ |
| Master-detail | ✅ | 🔴 | ✅ |
| Lazy loading | ✅ | 🔴 | ✅ (server-side row model) |
| Live pagination | ✅ | 🔴 | ✅ |
| Row + column virtualization | ✅ | ✅ | ✅ |
| Cell editing | ✅ | ✅ | ✅ |
| Cell selection | ✅ | 🔴 | ✅ (range selection) |
| Row selection | ✅ | ✅ | ✅ |
| Context menus | ✅ | 🔴 | ✅ |
| Keyboard navigation | ✅ | ✅ | ✅ |
| Theming (CSS variables) | ✅ | ✅ | ✅ |
| Excel export | 🔴 | 🔴 | ✅ |
| Clipboard | 🔴 | 🔴 | ✅ |
| Integrated charting | 🔴 | 🔴 | ✅ |
| Server-side row model | ✅ | 🔴 | ✅ |
| Status bar / sidebar panels | 🔴 | 🔴 | ✅ |
AG Grid Community (MIT) does not include row grouping, pivoting, aggregations, tree data, or master-detail. Those require AG Grid Enterprise. Infinite Table includes all of these in the free build (with a "Powered by Infinite Table" footer). A paid license key removes the footer.
## Pricing
| | Infinite Table | AG Grid Enterprise |
|---|---|---|
| **Starting price** | [$395/dev/year](https://infinite-table.com/pricing) | [~$999/dev/year](https://www.ag-grid.com/license-pricing) |
| **Volume discount** | 5% at 3 devs, 10% at 5, 15% at 10 | Contact sales |
| **Deployment license** | None required | None required |
| **Free tier** | All features, footer displayed | Community edition (grouping/pivot excluded) |
| **Support** | Email (paid license) | Zendesk (Enterprise license) |
## When AG Grid is the better choice
- **Multi-framework projects.** If you need the same data grid across Angular, Vue, and React codebases, AG Grid is the clear choice — Infinite Table is React-only. Standardising on one grid across frameworks saves training time and keeps behaviour consistent.
- **The widest feature surface.** AG Grid Enterprise includes built-in charting, clipboard, Excel export, column tool panels, status bars, and a full server-side row model with partial store. If you need several of these features, AG Grid covers them all in one package. No other grid matches this breadth.
- **Enormous community and ecosystem.** With over 1M weekly npm downloads and 13k+ GitHub stars, AG Grid has the deepest community resources, Stack Overflow coverage, and third-party integrations of any data grid. Ecosystem maturity matters — and AG Grid's is unmatched.
- **Your team already knows AG Grid.** If your developers are experienced with AG Grid's API and patterns from other projects, that familiarity has real value. Switching to a different grid has a learning cost, and AG Grid's comprehensive documentation makes it possible to find an answer for almost any scenario.
- **You need the dedicated configuration options.** AG Grid's large API means there's often a purpose-built prop for a specific edge case. If you regularly need that level of fine-grained, per-feature configuration, the breadth of the API is a strength.
## When Infinite Table is the better fit
- **You want the grid to feel like React.** Infinite Table's API is props, controlled state, and JSX — the same patterns you use in every other React component. If your team thinks in React, Infinite Table fits that mental model.
- **You prefer a small, composable API.** Infinite Table ships grouping, pivoting, and aggregations with a compact configuration surface. Function props as building blocks, controlled/uncontrolled patterns for state, composable rather than exhaustive. A different bet from AG Grid's comprehensive approach — one that suits teams who want fewer props to learn and coordinate.
- **You need grouping, pivoting, and aggregations without an enterprise license.** These are included in Infinite Table's free build. AG Grid reserves them for the Enterprise tier.
- **Simpler licensing.** One plan, one key for the whole team, no deployment license.
Install the package, render your first DataGrid, and learn how `` and `` work together.
Grouping, aggregations, and pivoting — included without an Enterprise license.
Use Infinite Table free with a footer, or buy a license to remove it and get email support.
Read AG Grid's official React getting started guide.
## Help us keep this comparison up-to-date
This page is our reading of AG Grid's public docs and pricing as of mid-2026. We want it to stay accurate. If you work on AG Grid — or you've spotted something that's wrong, outdated, or missing context — please tell us. We will update the page.
- [Edit this page on GitHub](https://github.com/infinite-table/infinite-react/edit/master/www/content/docs/learn/compare/ag-grid.page.md) and open a pull request
- [File a correction issue](https://github.com/infinite-table/infinite-react/issues/new?template=compare_page_correction.md&title=Compare%20page%20correction%3A%20AG%20Grid)
- Email [admin@infinite-table.com](mailto:admin@infinite-table.com?subject=Compare%20page%20correction%3A%20AG%20Grid) with the URL and what should change
---
# Infinite Table vs MUI X Data Grid
> A detailed comparison of Infinite Table and MUI X Data Grid. React-native design approaches, features across tiers, and when MUI X Data Grid is the better choice.
Canonical page: https://infinite-table.com/docs/learn/compare/mui-x-data-grid
[MUI X Data Grid](https://mui.com/x/react-data-grid/) is a React data grid from the Material UI team. It's part of the broader MUI X suite (date pickers, charts, tree view) and follows Material Design conventions. Like Infinite Table, it's React-only and uses a declarative, prop-driven API.
These two grids have more in common architecturally than either has with AG Grid or TanStack Table. Both render through React, both use props and controlled state, both support JSX cell renderers. The differences are in design-system coupling, feature availability across tiers, and how each grid's API is structured.
## Where they diverge
**Design-system coupling.** MUI X Data Grid is built on Material UI. It inherits your MUI theme tokens — palette, spacing, typography — automatically. This is a major advantage if your app already uses MUI. If your app uses a different design system, you'll be adding MUI's styling infrastructure (`@emotion`, theme provider, `sx` prop) as dependencies alongside your existing stack.
Infinite Table is design-system agnostic. Theming is done through CSS variables — you can integrate with Tailwind, vanilla CSS, or any design system without extra dependencies. There's no coupling to a specific component library.
**Feature availability.** MUI X uses a four-tier model: Community (free), Pro ($299/dev/yr), Premium ($599/dev/yr), and Enterprise ($1,399/dev/yr). Core features like column resizing, pinning, and tree data require at least Pro. Grouping, pivoting, and aggregations require Premium.
Infinite Table includes all of these in the package (only one package, no separate community and enterprise packages). A "Powered by Infinite Table" footer is displayed; a [paid license ($395/dev/year)](https://infinite-table.com/pricing) removes it.
**Data layer separation.** Infinite Table splits data management and rendering into two React components — `` and ``. The `` handles fetching, sorting, grouping, pivoting, and filtering; the `` handles rendering. You can even use `` with your own custom component. MUI X Data Grid is a single component that handles both data and rendering internally.
## Architecture
| | Infinite Table | MUI X Data Grid |
|---|---|---|
| **Framework** | React | React |
| **Design system** | Agnostic — CSS variables | Material UI — MUI theme system |
| **Component model** | Two components: `` + `` | Single `` / `` / `` component |
| **API style** | Declarative props, controlled + uncontrolled | Declarative props, controlled + uncontrolled |
| **Cell renderers** | JSX components via column `render` prop | JSX components via `renderCell` slot |
| **TypeScript** | Written in TypeScript | Written in TypeScript |
| **Virtualization** | Row + column | Row virtualization; column virtualization in Pro+ |
| **Packages** | Single package, all features | Separate packages per tier |
## Feature Comparison
| Feature | Infinite Table (free) | MUI X Community (free) | MUI X Pro ($299/dev/yr) | MUI X Premium ($599/dev/yr) |
|---|---|---|---|---|
| Sorting (single + multi) | ✅ | ✅ (single) | ✅ (multi) | ✅ (multi) |
| Column filtering | ✅ | ✅ (single) | ✅ (multi) | ✅ (multi) |
| Column resizing | ✅ | 🔴 | ✅ | ✅ |
| Column reordering | ✅ | 🔴 | ✅ | ✅ |
| Column pinning | ✅ | 🔴 | ✅ | ✅ |
| Column grouping (headers) | ✅ | ✅ | ✅ | ✅ |
| Row grouping | ✅ | 🔴 | 🔴 | ✅ |
| Aggregations | ✅ | 🔴 | 🔴 | ✅ |
| Pivoting | ✅ | 🔴 | 🔴 | ✅ |
| Tree data | ✅ | 🔴 | ✅ | ✅ |
| Master-detail | ✅ | 🔴 | ✅ | ✅ |
| Row virtualization | ✅ | ✅ | ✅ | ✅ |
| Column virtualization | ✅ | 🔴 | ✅ | ✅ |
| Cell editing | ✅ | ✅ | ✅ | ✅ |
| Cell selection | ✅ | 🔴 | 🔴 | ✅ |
| Row selection | ✅ | ✅ | ✅ | ✅ |
| Keyboard navigation | ✅ | ✅ | ✅ | ✅ |
| Lazy loading | ✅ | 🔴 | ✅ (server-side) | ✅ |
| Live pagination | ✅ | 🔴 | 🔴 | 🔴 |
| Context menus | ✅ | 🔴 | 🔴 | 🔴 |
| Excel export | 🔴 | 🔴 | 🔴 | ✅ |
| Clipboard (copy/paste) | 🔴 | 🔴 | 🔴 | ✅ |
MUI X uses a tiered model: row grouping, pivoting, aggregations, and cell selection require the Premium plan ($599/dev/year). Column resizing, pinning, reordering, tree data, and master-detail require at least Pro ($299/dev/year). Feature details are from the [MUI pricing page](https://mui.com/pricing/).
Infinite Table includes all of these features in the free Community build.
## Pricing
| | Infinite Table | MUI X Pro | MUI X Premium | MUI X Enterprise |
|---|---|---|---|---|
| **Price** | [$395/dev/year](https://infinite-table.com/pricing) | [$299/dev/year](https://mui.com/pricing/) | [$599/dev/year](https://mui.com/pricing/) | [$1,399/dev/year](https://mui.com/pricing/) |
| **Grouping + pivoting** | ✅ (free) | 🔴 | ✅ | ✅ |
| **Tree data + master-detail** | ✅ (free) | ✅ | ✅ | ✅ |
| **Column resizing + pinning** | ✅ (free) | ✅ | ✅ | ✅ |
| **Deployment license** | None | None | None | None |
| **Support** | Email (paid) | Community | Priority over Community | Priority over Pro |
## When MUI X Data Grid is the better choice
- **You're already in the MUI ecosystem.** If your app uses Material UI, MUI X Data Grid inherits your MUI theme automatically — palette, spacing, typography, dark mode — with zero configuration. Infinite Table uses CSS variables and won't pick up MUI theme tokens automatically.
- **You need the full MUI X suite.** MUI X includes date pickers, charts, tree view, and a scheduler under a single license. If you need multiple MUI X components, a Pro or Premium license covers them all.
- **Material Design consistency.** The Data Grid follows Material Design patterns by default. If your design spec is Material Design, MUI X is the most natural fit.
- **Large community.** MUI has a very large user community — millions of weekly npm downloads for Material UI. More community resources, tutorials, and third-party integrations.
- **Column-level features at the Pro tier.** If you need column resizing, pinning, reordering, and tree data but not grouping or pivoting, MUI X Pro at $299/dev/year covers those features along with all other MUI X Pro components.
## When Infinite Table is the better fit
- **You need grouping, pivoting, and aggregations without the Premium tier.** These are free in Infinite Table's Community build. MUI X requires Premium ($599/dev/year) for the same features.
- **You're not using Material UI.** If your app uses Tailwind, vanilla CSS, or another design system, Infinite Table may be a simpler fit — it uses plain CSS variables and works with any styling approach without adding a design-system dependency.
- **Data layer separation.** Infinite Table's `` / `` split gives you a clean separation between data management (fetching, sorting, grouping, pivoting, filtering) and rendering. You can even replace `` with your own component and keep the data layer.
- **Column virtualization on the free tier.** Infinite Table virtualizes both rows and columns by default. MUI X Community only virtualizes rows up to 100 rows; column virtualization and unlimited row virtualization require Pro.
- **Live pagination and context menus.** Infinite Table includes built-in live pagination and context menus. MUI X does not offer equivalents at any tier.
- **Single-tier licensing.** Infinite Table has one plan with all features included. MUI X offers four tiers, which gives you flexibility to pay only for what you need — but also means checking which tier covers each feature.
Install the package, render your first DataGrid, and learn how `` and `` work together.
Theme Infinite Table without a Material UI theme provider — CSS variables only.
Use Infinite Table free with a footer, or buy a license to remove it and get email support.
Read MUI X Data Grid's official documentation.
## Help us keep this comparison up-to-date
This page is our reading of MUI X Data Grid's public docs and [pricing page](https://mui.com/pricing/) as of mid-2026. We want it to stay accurate. If you work on MUI X — or you've spotted something that's wrong, outdated, or missing context — please tell us. We will update the page.
- [Edit this page on GitHub](https://github.com/infinite-table/infinite-react/edit/master/www/content/docs/learn/compare/mui-x-data-grid.page.md) and open a pull request
- [File a correction issue](https://github.com/infinite-table/infinite-react/issues/new?template=compare_page_correction.md&title=Compare%20page%20correction%3A%20MUI%20X%20Data%20Grid)
- Email [admin@infinite-table.com](mailto:admin@infinite-table.com?subject=Compare%20page%20correction%3A%20MUI%20X%20Data%20Grid) with the URL and what should change
---
# Infinite Table vs TanStack Table
> A detailed comparison of Infinite Table and TanStack Table. Declarative rendered grid vs headless logic library — architecture, features, and when TanStack Table is the better choice.
Canonical page: https://infinite-table.com/docs/learn/compare/tanstack-table
[TanStack Table](https://tanstack.com/table) (formerly React Table) is a popular, MIT-licensed headless table library. It provides table logic — sorting, filtering, grouping, pagination — but no UI. You bring your own JSX, your own styles, your own virtualization, your own keyboard navigation.
Infinite Table is a fully rendered React data grid. You pass props — columns, data, grouping configuration — and get a complete, virtualized, keyboard-navigable grid with theming out of the box. Both are valid approaches. The question is where you want to spend your engineering time.
## Two ends of a spectrum
Data grids range from fully rendered components to headless logic libraries. At one end is AG Grid — a comprehensive, multi-framework grid that handles everything. At the other end is TanStack Table — headless hooks that give you total rendering control and require you to build everything visible.
Infinite Table sits in the middle: **a declarative React component that ships the grid**. You get a React-native API (props, controlled state, JSX cell renderers) without having to construct the table markup, virtualization, focus management, and accessibility yourself.
```tsx
// TanStack Table: you provide all the JSX
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
return (
);
```
```tsx
// Infinite Table: declarative props, grid ships complete
primaryKey="id" data={dataSource}>
columns={columns} />
```
Both examples display tabular data. The first gives you total control over every `
` and `
`. The second gives you a working grid — virtualized, keyboard-navigable, themeable — in two components.
## Architecture
| | Infinite Table | TanStack Table |
|---|---|---|
| **Type** | Rendered React component | Headless logic library |
| **What you get** | Full UI: virtualized grid, headers, cells, scrollbars, keyboard nav, theming | Table state + utilities; you provide all DOM and styling |
| **API style** | Declarative props (controlled + uncontrolled) | Hooks that return row/cell models; you render everything |
| **Cell rendering** | JSX — pass a React component as a column prop | JSX - Use the `FlexRender` component |
| **Frameworks** | React | All Most Popular |
| **Virtualization** | Built-in row + column virtualization | Not included; pair with [TanStack Virtual](https://tanstack.com/virtual) or your own |
| **TypeScript** | First-class | First-class |
| **Bundle** | Single package, includes CSS | Tiny core; total size depends on what you build on top |
## Feature Comparison
| Feature | Infinite Table | TanStack Table |
|---|---|---|
| Sorting | ✅ | 🔴 (logic only) |
| Column filtering | ✅ | 🔴 (logic only) |
| Row grouping | ✅ | 🔴 (logic only) |
| Aggregations | ✅ | 🔴 (logic only) |
| Pivoting | ✅ | 🔴 (logic only) |
| Tree data | ✅ | 🔴 (logic only) |
| Column resizing | ✅ | 🔴 (logic helpers) |
| Column reordering | ✅ | 🔴 |
| Column pinning | ✅ | 🔴 (logic helpers) |
| Cell editing | ✅ | 🔴 |
| Cell selection | ✅ | 🔴 |
| Row selection | ✅ | 🔴 (logic only) |
| Keyboard navigation | ✅ | 🔴 |
| Context menus | ✅ | 🔴 |
| Master-detail | ✅ | 🔴 |
| Lazy loading / live pagination | ✅ | 🔴 (pagination logic available) |
| Row + column virtualization | ✅ | 🔴 (use TanStack Virtual) |
| Theming | ✅ | 🔴 |
"Logic only" means TanStack Table handles the state and computations, but you write all the JSX, CSS, event handlers, and accessibility attributes. This is powerful but requires substantial development effort for a production-grade data grid.
## What "headless" means in practice
With TanStack Table, building a production data grid involves:
1. Rendering the `
`, ``, ``, `
`, `
` (or `
`-based layout) yourself.
2. Wiring up virtualization (typically TanStack Virtual) for large datasets.
3. Building filter UIs, sort indicators, group expand/collapse toggles, resize handles, and column reorder drag-and-drop.
4. Handling keyboard navigation and ARIA attributes for accessibility.
5. Styling everything from scratch or integrating with your design system.
This is the right approach when you need pixel-perfect control or are building a design-system component library. But it means weeks of work to reach feature parity with a rendered grid — and that code becomes yours to maintain.
With Infinite Table, grouping, pivoting, filtering, virtualization, keyboard navigation, and theming work the moment you render the component. You customise through props and JSX cell renderers, not by rebuilding the grid's internals.
## Pricing
TanStack Table is MIT-licensed and free. There is no paid tier.
Infinite Table is also free — all features included — but displays a "Powered by Infinite Table" footer. A [paid license ($395/dev/year)](https://infinite-table.com/pricing) removes the footer and adds email support.
## When TanStack Table is the better choice
- **Total rendering control.** You need pixel-perfect custom UI, or you're building a table component for a design-system library where the rendered output must match your design spec exactly.
- **Multi-framework.** TanStack Table works across React, Vue, Solid, and Svelte. Infinite Table is React-only.
- **Minimal bundle.** If you only need sorting and basic filtering on a small dataset (no virtualization, no grouping), TanStack Table's core is smaller than any full grid component.
- **Zero restrictions.** MIT license with no footer, no license key, no terms beyond MIT.
- **Existing investment.** If your team has already built a mature grid UI on top of TanStack Table, migrating to a rendered grid may not justify the effort.
## When Infinite Table is the better fit
- **You want to ship the grid, not build it.** Infinite Table delivers a production-ready grid — virtualized, keyboard-navigable, themed — out of the box. You focus on your product, not on re-implementing table infrastructure.
- **You want native React API feel without the assembly work.** TanStack Table gives you hooks and row models; you assemble the JSX. Infinite Table gives you declarative props and controlled state — the same patterns you use in every other React component — but ships the complete UI so you don't have to build it yourself. Of course you still have a lot of control over column cells, headers, filters, etc.
- **Complex data features built in.** Master-detail, tree grids, lazy loading, live pagination, cell editing, cell selection, and context menus are all included. Building these on top of TanStack Table is a significant engineering project.
- **Accessibility and keyboard support.** Infinite Table includes keyboard navigation and focus management. With TanStack Table, you implement these yourself.
Install the package, render your first DataGrid, and learn how `` and `` work together.
Built-in grouping, aggregations, and pivoting — without writing the UI layer yourself.
Use Infinite Table free with a footer, or buy a license to remove it and get email support.
Read TanStack Table's official introduction.
## Help us keep this comparison up-to-date
This page is our reading of TanStack Table's public docs as of mid-2026. We want it to stay accurate. If you work on TanStack Table — or you've spotted something that's wrong, outdated, or missing context — please tell us. We will update the page.
- [Edit this page on GitHub](https://github.com/infinite-table/infinite-react/edit/master/www/content/docs/learn/compare/tanstack-table.page.md) and open a pull request
- [File a correction issue](https://github.com/infinite-table/infinite-react/issues/new?template=compare_page_correction.md&title=Compare%20page%20correction%3A%20TanStack%20Table)
- Email [admin@infinite-table.com](mailto:admin@infinite-table.com?subject=Compare%20page%20correction%3A%20TanStack%20Table) with the URL and what should change
---
# Using Context Menus
> InfiniteTable DataGrid allows you to easily configure context menus for any row and cell in the table and for the whole table body.
Canonical page: https://infinite-table.com/docs/learn/context-menus/using-context-menus
The easiest way to configure a context menu is to provide the [`getCellContextMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getCellContextMenuItems) callback function and use it to return the menu items you want to show in the context menu.
```tsx
const getCellContextMenuItems = ({ column, value }) => {
if (column.id === 'currency') {
return [
{
label: `Convert ${value}`,
key: 'currency-convert',
},
];
}
if (column.id === 'age') {
return null;
}
return [
{
label: `Welcome ${value}`,
key: 'hi',
},
];
};
data={data} primaryKey="id">
getCellContextMenuItems={getCellContextMenuItems}
columns={columns}
/>
;
```
**Example: Using context menus**
Right-click any cell in the table to see the custom context menu.
```ts
import {
InfiniteTable,
DataSource,
InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
type Developer = {
id: number;
firstName: string;
lastName: string;
country: string;
city: string;
currency: string;
preferredLanguage: string;
stack: string;
canDesign: 'yes' | 'no';
hobby: string;
salary: number;
age: number;
};
const dataSource = () => {
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
.then((r) => r.json())
.then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns = {
stack: {
field: 'stack',
header: 'Stack',
},
firstName: {
field: 'firstName',
header: 'Name',
},
age: {
field: 'age',
header: 'Age',
},
hobby: {
field: 'hobby',
header: 'Hobby',
},
preferredLanguage: {
header: 'Language',
field: 'preferredLanguage',
},
};
export default function App() {
return (
<>
primaryKey="id" data={dataSource}>
debugId="basic-cells-context-menu-example"
columns={columns}
getCellContextMenuItems={({ data, column }) => {
return [
{
key: 'hello',
label: `Hello, ${data?.lastName} ${data?.firstName}`,
onClick: () => {
alert(`Hello, ${data?.lastName} ${data?.firstName}`);
},
},
{
key: 'col',
label: `Current clicked column: ${column.header}`,
},
{
key: 'learn',
label: `Learn`,
menu: {
items: [
{
key: 'backend',
label: 'Backend',
onClick: () => {
alert(
`Learn Backend, ${data?.lastName} ${data?.firstName}`,
);
},
},
{
key: 'frontend',
label: 'Frontend',
onClick: () => {
alert(
`Learn Frontend, ${data?.lastName} ${data?.firstName}`,
);
},
},
],
},
},
];
}}
/>
>
);
}
```
The [`getCellContextMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getCellContextMenuItems) function can return one of the following:
- `null` - no custom context menu will be displayed, the default context menu will be shown (default event behavior not prevented)
- `[]` - an empty array - no custom context menu will be displayed, but the default context menu is not shown - the default event behavior is prevented
- `Array