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}
COPY
View Mode
Fork Forkimport { 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 thecolumnsprop - 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, ornullif 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: booleansomeRowsSelected: booleanrenderBag- 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 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.
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
Customizing_the_column_sort_icon
renderSortIcon({ columnSortInfo }) { if (!columnSortInfo) { return ' 🤷♂️'; } return columnSortInfo.dir === 1 ? '▲' : '▼'; }
COPY
or you can use the
useInfiniteHeaderCell hook to get the same information. Customizing_the_column_sort_icon
import { useInfiniteHeaderCell, } from '@infinite-table/infinite-react'; /// ... renderSortIcon(){ const { columnSortInfo } = useInfiniteHeaderCell(); if (!columnSortInfo) { return ' 🤷♂️'; } return columnSortInfo.dir === 1 ? '▲' : '▼'; },
COPY
View Mode
Fork Forkimport { 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
Customizing_the_menu_icon
renderMenuIcon({ column }) { return `🔧 ${column.id}`; }
COPY
or you can use the
useInfiniteHeaderCell hook to get the same information. Customizing_the_menu_icon
import { useInfiniteHeaderCell, } from '@infinite-table/infinite-react'; /// ... renderMenuIcon(){ const { column } = useInfiniteHeaderCell(); return `🔧 ${column.id}`; },
COPY
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.View Mode
Fork Forkimport { 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> ); }
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 Customizing_the_filter_icon
renderFilterIcon({ filtered }) { return filtered ? '🔍' : ''; }
COPY
or you can use the
useInfiniteHeaderCell hook to get the same information. Customizing_the_menu_icon
import { useInfiniteHeaderCell, } from '@infinite-table/infinite-react'; /// ... renderMenuIcon(){ const { filtered } = useInfiniteHeaderCell(); return filtered ? '🔥' : ''; },
COPY
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.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.View Mode
Fork Forkimport * 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.View Mode
Fork Forkimport * 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.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.View Mode
Fork Forkimport { 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: 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; };