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
the column is bound to. If you want to customize this, use the header
property.
type Developer = {
id: string;
firstName: string;
lastName: string;
age: number;
};
const columns: InfiniteTablePropColumns<Developer> = {
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}`,
},
};
import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { 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('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k') .then((r) => r.json()) .then((data: Developer[]) => data); }; const domProps = { style: { minHeight: 300, }, }; export default function App() { return ( <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> domProps={domProps} columns={columns} /> </DataSource>
Having the header
property be a strin value is useful but when you want more flexibility, you can use a function instead.
When the column 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 thecolumns
prop - but rather an enhanced version of that, which contains additional properties and computed values. It is called a "computed" column - typed asInfiniteTableComputedColumn<DATA_TYPE>
.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, ornull
if the column is not sorted.api
- a reference to the table API object.columnApi
- a reference to the table Column API 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).
Note
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 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
-
renderFilterIcon
-
renderMenuIcon
-
renderSelectionCheckBox
-
renderHeaderSelectionCheckBox
-
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
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
call, renderBag.filterIcon
- the result of the renderFilterIcon
call, etc).
So if you specify a custom 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 iconfilterIcon
- the filter icon - displayed when the current column is used in filteringfilterEditor
- the current filter editormenuIcon
- the menu icon that can be clicked to open the column menuselectionCheckBox
- the selection check box - displays the current selection status and controls the selection for all rows.all
- all of the above combined together in aReact.Fragment
.
Customizing the Sort Icon
For customizing the sort icon, use the column.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
renderSortIcon({ columnSortInfo }) {
if (!columnSortInfo) {
return ' 🤷♂️';
}
return columnSortInfo.dir === 1 ? '▲' : '▼';
}
or you can use the useInfiniteHeaderCell
hook to get the same information.
import {
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
/// ...
renderSortIcon(){
const { columnSortInfo } = useInfiniteHeaderCell();
if (!columnSortInfo) {
return ' 🤷♂️';
}
return columnSortInfo.dir === 1 ? '▲' : '▼';
},
import { InfiniteTable, DataSource, InfiniteTablePropColumns, useInfiniteHeaderCell, } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { 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('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k') .then((r) => r.json()) .then((data: Developer[]) => data); }; const domProps = { style: { minHeight: 300, }, }; export default function App() { return ( <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> domProps={domProps} columns={columns} /> </DataSource>
Customizing the Menu Icon
For customizing the menu icon, use the column.renderMenuIcon
function.
Inside that function you can either use the object passed as a parameter to get information about the column
renderMenuIcon({ column }) {
return `🔧 ${column.id}`;
}
or you can use the useInfiniteHeaderCell
hook to get the same information.
import {
useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
/// ...
renderMenuIcon(){
const { column } = useInfiniteHeaderCell();
return `🔧 ${column.id}`;
},
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.
import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, renderMenuIcon: false, }, name: { header: 'Name', // custom column header label valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`, // custom menu icon renderMenuIcon: () => <div>🌎</div>, }, age: { field: 'age', header: 'Age', renderMenuIcon: ({ column }) => { return `🔧 ${column.id}`; }, }, }; type Developer = { id: number; firstName: string; lastName: string; age: number; }; const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k') .then((r) => r.json()) .then((data: Developer[]) => data); }; const domProps = { style: { minHeight: 300, }, }; export default function App() { return ( <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> domProps={domProps} columns={columns} /> </DataSource>
Note
If you don't want to show a column menu (icon) at all, you can set the column.renderMenuIcon
prop to false
.
Also, see the column.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
function.
Inside that function you can either use the object passed as a parameter to get information about the filtered
state of the column
renderFilterIcon({ filtered }) {
return filtered ? '🔍' : '';
}
or you can use the useInfiniteHeaderCell
hook to get the same information.
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
function to determine if the column is filtered or not and render a different header label.
Note
If specified, the column.renderFilterIcon
function prop is called even if the column is not currently filtered.
The salary
column will show a bolded label when filtered.
The firstName
column will show a custom filter icon when filtered.
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<Developer> = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`) .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', type: 'number', defaultWidth: 100, }, salary: { field: 'salary', type: 'number', header: ({ filtered }) => { return filtered ? <b>Salary</b> : '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 ( <> <React.StrictMode> <DataSource<Developer> data={data} primaryKey="id" defaultFilterValue={[]} filterDelay={0} filterMode="local" > <InfiniteTable<Developer> domProps={domProps} columnDefaultWidth={150} columnMinWidth={50} columns={columns} /> </DataSource> </React.StrictMode> </>
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
to hide the filters from under the column header.
Next, you can use the column.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.
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<Developer> = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`) .then((r) => r.json()) .then((data: Developer[]) => data); }; const FilterIcon = () => ( <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon> </svg> ); 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<HTMLDivElement>(null); return ( <div onMouseDown={(e) => { if (e.nativeEvent) { // @ts-ignore e.nativeEvent.__insideMenu = column.id; } }} onPointerDown={(event) => { event.stopPropagation(); if (visible) { setVisible(false); return; } setVisible(true); function handleMouseDown(event: MouseEvent) { // @ts-ignore if (event.__insideMenu !== column.id) { setVisible(false); document.documentElement.removeEventListener( 'mousedown', handleMouseDown, ); } } document.documentElement.addEventListener('mousedown', handleMouseDown); }} > <FilterIcon /> {createPortal( <div ref={domRef} style={{ position: 'absolute', color: 'white', top: 0, overflow: 'visible', background: 'var(--infinite-background)', border: `var(--infinite-cell-border)`, left: 0, width: column.computedWidth, padding: 10, display: visible ? 'block' : 'none', }} > {renderBag.filterEditor} </div>, portalContainer!, )} </div> ); } const customHeaderWithFilterMenu: InfiniteTableColumn<any>['renderHeader'] = ({ renderBag, }) => { return ( <> {renderBag.header} <div style={{ flex: 1 }}></div> {renderBag.filterIcon} {renderBag.menuIcon} <ColumnFilterMenuIcon /> </> ); }; const columns: InfiniteTablePropColumns<Developer> = { 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 ( <> <React.StrictMode> <DataSource<Developer> data={data} primaryKey="id" defaultFilterValue={[]} shouldReloadData={{ filterValue: false, sortInfo: false, groupBy: false, pivotBy: false, }} > <InfiniteTable<Developer> showColumnFilters={false} columnDefaultWidth={150} columnMinWidth={50} columns={columns} /> </DataSource> </React.StrictMode> </>
Customizing the Selection Checkbox
For customizing the selection checkbox in the column header, use the column.renderHeaderSelectionCheckBox
function.
Note
If you want another column, other than the group column, to show a selection checkbox, you have to also set the column.renderSelectionCheckBox
prop to true
.
The group column, as well as the stack
column display a custom selection checkbox in the column header.
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<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', defaultHiddenWhenGroupedBy: '*', }, stack: { renderSelectionCheckBox: true, renderHeaderSelectionCheckBox: ({ renderBag }) => { // render the default value and decorate it return <b>[{renderBag.selectionCheckBox}]</b>; }, field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, preferredLanguage: { field: 'preferredLanguage', }, canDesign: { field: 'canDesign', }, }; const defaultGroupBy: DataSourceProps<Developer>['groupBy'] = [ { field: 'canDesign', }, { field: 'stack', }, { field: 'preferredLanguage', }, ]; const groupColumn: InfiniteTableProps<Developer>['groupColumn'] = { field: 'firstName', renderHeaderSelectionCheckBox: ({ renderBag }) => { // render the default value and decorate it return <b>[{renderBag.selectionCheckBox}]</b>; }, defaultWidth: 300, }; const domProps = { style: { flex: 1, minHeight: 500, }, }; export default function App() { return ( <DataSource<Developer> data={dataSource} groupBy={defaultGroupBy} selectionMode="multi-row" primaryKey="id" > <InfiniteTable<Developer> columns={columns} domProps={domProps} groupColumn={groupColumn} columnDefaultWidth={150} /> </DataSource> ); } const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id