React TreeTable is used to display hierarchical data in tabular format.
Setup
Refer to PrimeReact setup documentation for download and installation steps for your environment.
Import
import { TreeTable } from 'primereact/treetable';
Getting Started
TreeTable component requires an array of TreeNode objects as its value and columns defined with one or more Column components.
Here is a sample json response to serve as the datasource of the TreeTable.
{
"root":
[
{
"key": "0",
"data":{
"name":"Applications",
"size":"100kb",
"type":"Folder"
},
"children":[
{
"key": "0-0",
"data":{
"name":"React",
"size":"25kb",
"type":"Folder"
},
"children":[
{
"key": "0-0-0",
"data":{
"name":"react.app",
"size":"10kb",
"type":"Application"
}
},
{
"key": "0-0-1",
"data":{
"name":"native.app",
"size":"10kb",
"type":"Application"
}
},
{
"key": "0-0-2",
"data":{
"name":"mobile.app",
"size":"5kb",
"type":"Application"
}
}
]
},
{
"key": "0-1",
"data":{
"name":"editor.app",
"size":"25kb",
"type":"Application"
}
},
{
"key": "0-2",
"data":{
"name":"settings.app",
"size":"50kb",
"type":"Application"
}
}
]
},
{
"key": "1",
"data":{
"name":"Cloud",
"size":"20kb",
"type":"Folder"
},
"children":[
{
"key": "1-0",
"data":{
"name":"backup-1.zip",
"size":"10kb",
"type":"Zip"
}
},
{
"key": "1-1",
"data":{
"name":"backup-2.zip",
"size":"10kb",
"type":"Zip"
}
}
]
},
{
"key": "2",
"data": {
"name":"Desktop",
"size":"150kb",
"type":"Folder"
},
"children":[
{
"key": "2-0",
"data":{
"name":"note-meeting.txt",
"size":"50kb",
"type":"Text"
}
},
{
"key": "2-1",
"data":{
"name":"note-todo.txt",
"size":"100kb",
"type":"Text"
}
}
]
},
{
"key": "3",
"data":{
"name":"Documents",
"size":"75kb",
"type":"Folder"
},
"children":[
{
"key": "3-0",
"data":{
"name":"Work",
"size":"55kb",
"type":"Folder"
},
"children":[
{
"key": "3-0-0",
"data":{
"name":"Expenses.doc",
"size":"30kb",
"type":"Document"
}
},
{
"key": "3-0-1",
"data":{
"name":"Resume.doc",
"size":"25kb",
"type":"Resume"
}
}
]
},
{
"key": "3-1",
"data":{
"name":"Home",
"size":"20kb",
"type":"Folder"
},
"children":[
{
"key": "3-1-0",
"data":{
"name":"Invoices",
"size":"20kb",
"type":"Text"
}
}
]
}
]
},
{
"key": "4",
"data": {
"name":"Downloads",
"size":"25kb",
"type":"Folder"
},
"children":[
{
"key": "4-0",
"data": {
"name":"Spanish",
"size":"10kb",
"type":"Folder"
},
"children":[
{
"key": "4-0-0",
"data":{
"name":"tutorial-a1.txt",
"size":"5kb",
"type":"Text"
}
},
{
"key": "4-0-1",
"data":{
"name":"tutorial-a2.txt",
"size":"5kb",
"type":"Text"
}
}
]
},
{
"key": "4-1",
"data":{
"name":"Travel",
"size":"15kb",
"type":"Text"
},
"children":[
{
"key": "4-1-0",
"data":{
"name":"Hotel.pdf",
"size":"10kb",
"type":"PDF"
}
},
{
"key": "4-1-1",
"data":{
"name":"Flight.pdf",
"size":"5kb",
"type":"PDF"
}
}
]
}
]
},
{
"key": "5",
"data": {
"name":"Main",
"size":"50kb",
"type":"Folder"
},
"children":[
{
"key": "5-0",
"data":{
"name":"bin",
"size":"50kb",
"type":"Link"
}
},
{
"key": "5-1",
"data":{
"name":"etc",
"size":"100kb",
"type":"Link"
}
},
{
"key": "5-2",
"data":{
"name":"var",
"size":"100kb",
"type":"Link"
}
}
]
},
{
"key": "6",
"data":{
"name":"Other",
"size":"5kb",
"type":"Folder"
},
"children":[
{
"key": "6-0",
"data":{
"name":"todo.txt",
"size":"3kb",
"type":"Text"
}
},
{
"key": "6-1",
"data":{
"name":"logo.png",
"size":"2kb",
"type":"Picture"
}
}
]
},
{
"key": "7",
"data":{
"name":"Pictures",
"size":"150kb",
"type":"Folder"
},
"children":[
{
"key": "7-0",
"data":{
"name":"barcelona.jpg",
"size":"90kb",
"type":"Picture"
}
},
{
"key": "7-1",
"data":{
"name":"primeng.png",
"size":"30kb",
"type":"Picture"
}
},
{
"key": "7-2",
"data":{
"name":"prime.jpg",
"size":"30kb",
"type":"Picture"
}
}
]
},
{
"key": "8",
"data":{
"name":"Videos",
"size":"1500kb",
"type":"Folder"
},
"children":[
{
"key": "8-0",
"data":{
"name":"primefaces.mkv",
"size":"1000kb",
"type":"Video"
}
},
{
"key": "8-1",
"data":{
"name":"intro.avi",
"size":"500kb",
"type":"Video"
}
}
]
}
]
}
Throughout the samples, a NodeService would be used to connect to a server to fetch the nodes with axios. Note that this is only for demo purposes, TreeTable does not have any restrictions on how data is provided.
import axios from 'axios';
export class NodeService {
getTreeTableNodes() {
return axios.get('showcase/demo/data/treetablenodes.json')
.then(res => res.data.root);
}
}
Following sample TreeTable has 3 columns and retrieves the data from the service on componentDidMount. Notice the expander property in the name column to indicate that this column displays an icon to toggle the child nodes.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { NodeService } from '../service/NodeService';
export const TreeTableDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
return (
<TreeTable value={nodes}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
);
}
Dynamic columns are also possible by creating the column component dynamically.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { NodeService } from '../service/NodeService';
export const TreeTableDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
let cols = [
{field: 'name', header: 'Name'},
{field: 'size', header: 'Size'},
{field: 'type', header: 'Type'}
];
let dynamicColumns = cols.map((col,i) => {
return <Column key={col.field} field={col.field} header={col.header} />;
});
return (
<TreeTable value={nodes}>
{dynamicColumns}
</TreeTable>
);
}
Controlled vs Uncontrolled
Expansion state is managed in two ways, in uncontrolled mode only initial expanded state of a node can be defined using expandedKeys property whereas in controlled mode expandedKeys property along with onToggle properties are used for full control over the state. If you need to expand or collapse the state of nodes programmatically then controlled mode should be used. Example below demonstrates both cases;
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { NodeService } from '../service/NodeService';
export class TreeTableDemo = () => {
const [nodes, setNodes] = useState([]);
const [expandedKeys, setExpandedKeys] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNdes(data));
}, [])
const toggleApplications = () => {
let expandedKeys = {...expandedKeys};
if (expandedKeys['0'])
delete expandedKeys['0'];
else
expandedKeys['0'] = true;
setExpandedKeys(expandedKeys);
}
return (
<div>
<h5>Uncontrolled</h5>
<TreeTable value={nodes}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Controlled</h5>
<Button onClick={toggleApplications} label="Toggle Applications" />
<TreeTable value={nodes} expandedKeys={expandedKeys}
onToggle={e => setExpandedKeys(e.value)} style={{marginTop: '.5em'}}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
</div>
)
}
Table Layout
Default table-layout is fixed meaning the cell widths do not depend on their content. If you require cells to scale based on their contents set autoLayout property to true. Note that auto layout cannot be supported in Scrollable or Resizable columns.
Templates
Field data of a corresponding row is displayed as the cell content by default, this can be customized using templating where current row data and column properties are passed to the body template. On the other hand, header and footer properties of a column are used to define the content of these sections by accepting either simple string values or JSX for advanced content. Similarly TreeTable itself also provides header and footer properties for the main header and footer of the table.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { NodeService } from '../service/NodeService';
export const TreeTableTemplatingDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
const actionTemplate = (node, column) => {
return <div>
<Button type="button" icon="pi pi-search" className="p-button-success" style={{marginRight: '.5em'}}></Button>
<Button type="button" icon="pi pi-pencil" className="p-button-warning"></Button>
</div>;
}
render() {
const header = "File Viewer";
const footer = <div style={{textAlign:'left'}}><Button icon="pi pi-refresh" tooltip="Reload"/></div>;
return (
<TreeTable value={nodes} header={header} footer={footer}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<Column body={actionTemplate} style={{textAlign:'center', width: '8em'}}/>
</TreeTable>
)
}
}
Column Group
Columns can be grouped at header and footer sections by defining a ColumnGroup component as the headerColumnGroup and footerColumnGroup properties.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { ColumnGroup } from 'primereact/columngroup';
import { Row } from 'primereact/row';
import { NodeService } from '../service/NodeService';
export const TreeTableColGroupDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
setNodes(getSales())
}, [])
const getSales = () => {
return [
{
key: '0',
data: { brand: 'Bliss', lastYearSale: '51%', thisYearSale: '40%', lastYearProfit: '$54,406.00', thisYearProfit: '$43,342'},
children: [
{
key: '0-0',
data: { brand: 'Product A', lastYearSale: '25%', thisYearSale: '20%', lastYearProfit: '$34,406.00', thisYearProfit: '$23,342' },
children: [
{
key: '0-0-0',
data: { brand: 'Product A-1', lastYearSale: '20%', thisYearSale: '10%', lastYearProfit: '$24,406.00', thisYearProfit: '$13,342' },
},
{
key: '0-0-1',
data: { brand: 'Product A-2', lastYearSale: '5%', thisYearSale: '10%', lastYearProfit: '$10,000.00', thisYearProfit: '$10,000' },
}
]
},
{
key: '0-1',
data: { brand: 'Product B', lastYearSale: '26%', thisYearSale: '20%', lastYearProfit: '$24,000.00', thisYearProfit: '$23,000' },
}
]
},
{
key: '1',
data: { brand: 'Fate', lastYearSale: '83%', thisYearSale: '96%', lastYearProfit: '$423,132', thisYearProfit: '$312,122' },
children: [
{
key: '1-0',
data: { brand: 'Product X', lastYearSale: '50%', thisYearSale: '40%', lastYearProfit: '$223,132', thisYearProfit: '$156,061' },
},
{
key: '1-1',
data: { brand: 'Product Y', lastYearSale: '33%', thisYearSale: '56%', lastYearProfit: '$200,000', thisYearProfit: '$156,061' },
}
]
},
{
key: '2',
data: { brand: 'Ruby', lastYearSale: '38%', thisYearSale: '5%', lastYearProfit: '$12,321', thisYearProfit: '$8,500' },
children: [
{
key: '2-0',
data: { brand: 'Product M', lastYearSale: '18%', thisYearSale: '2%', lastYearProfit: '$10,300', thisYearProfit: '$5,500' },
},
{
key: '2-1',
data: { brand: 'Product N', lastYearSale: '20%', thisYearSale: '3%', lastYearProfit: '$2,021', thisYearProfit: '$3,000' },
}
]
},
{
key: '3',
data: { brand: 'Sky', lastYearSale: '49%', thisYearSale: '22%', lastYearProfit: '$745,232', thisYearProfit: '$650,323' },
children: [
{
key: '3-0',
data: { brand: 'Product P', lastYearSale: '20%', thisYearSale: '16%', lastYearProfit: '$345,232', thisYearProfit: '$350,000' },
},
{
key: '3-1',
data: { brand: 'Product R', lastYearSale: '29%', thisYearSale: '6%', lastYearProfit: '$400,009', thisYearProfit: '$300,323' },
}
]
},
{
key: '4',
data: { brand: 'Comfort', lastYearSale: '17%', thisYearSale: '79%', lastYearProfit: '$643,242', thisYearProfit: '500,332' },
children: [
{
key: '4-0',
data: { brand: 'Product S', lastYearSale: '10%', thisYearSale: '40%', lastYearProfit: '$243,242', thisYearProfit: '$100,000' },
},
{
key: '4-1',
data: { brand: 'Product T', lastYearSale: '7%', thisYearSale: '39%', lastYearProfit: '$400,00', thisYearProfit: '$400,332' },
}
]
},
{
key: '5',
data: { brand: 'Merit', lastYearSale: '52%', thisYearSale: ' 65%', lastYearProfit: '$421,132', thisYearProfit: '$150,005' },
children: [
{
key: '5-0',
data: { brand: 'Product L', lastYearSale: '20%', thisYearSale: '40%', lastYearProfit: '$121,132', thisYearProfit: '$100,000' },
},
{
key: '5-1',
data: { brand: 'Product G', lastYearSale: '32%', thisYearSale: '25%', lastYearProfit: '$300,000', thisYearProfit: '$50,005' },
}
]
},
{
key: '6',
data: { brand: 'Violet', lastYearSale: '82%', thisYearSale: '12%', lastYearProfit: '$131,211', thisYearProfit: '$100,214' },
children: [
{
key: '6-0',
data: { brand: 'Product SH1', lastYearSale: '30%', thisYearSale: '6%', lastYearProfit: '$101,211', thisYearProfit: '$30,214' },
},
{
key: '6-1',
data: { brand: 'Product SH2', lastYearSale: '52%', thisYearSale: '6%', lastYearProfit: '$30,000', thisYearProfit: '$70,000' },
}
]
},
{
key: '7',
data: { brand: 'Dulce', lastYearSale: '44%', thisYearSale: '45%', lastYearProfit: '$66,442', thisYearProfit: '$53,322' },
children: [
{
key: '7-0',
data: { brand: 'Product PN1', lastYearSale: '22%', thisYearSale: '25%', lastYearProfit: '$33,221', thisYearProfit: '$20,000' },
},
{
key: '7-1',
data: { brand: 'Product PN2', lastYearSale: '22%', thisYearSale: '25%', lastYearProfit: '$33,221', thisYearProfit: '$33,322' },
}
]
},
{
key: '8',
data: { brand: 'Solace', lastYearSale: '90%', thisYearSale: '56%', lastYearProfit: '$765,442', thisYearProfit: '$296,232' },
children: [
{
key: '8-0',
data: { brand: 'Product HT1', lastYearSale: '60%', thisYearSale: '36%', lastYearProfit: '$465,000', thisYearProfit: '$150,653' },
},
{
key: '8-1',
data: { brand: 'Product HT2', lastYearSale: '30%', thisYearSale: '20%', lastYearProfit: '$300,442', thisYearProfit: '$145,579' },
}
]
},
{
key: '9',
data: { brand: 'Essence', lastYearSale: '75%', thisYearSale: '54%', lastYearProfit: '$21,212', thisYearProfit: '$12,533' },
children: [
{
key: '9-0',
data: { brand: 'Product TS1', lastYearSale: '50%', thisYearSale: '34%', lastYearProfit: '$11,000', thisYearProfit: '$8,562' },
},
{
key: '9-1',
data: { brand: 'Product TS2', lastYearSale: '25%', thisYearSale: '20%', lastYearProfit: '$11,212', thisYearProfit: '$3,971' },
}
]
}
];
}
const headerGroup = (
<ColumnGroup>
<Row>
<Column header="Brand" rowSpan={3} />
<Column header="Sale Rate" colSpan={4} />
</Row>
<Row>
<Column header="Sales" colSpan={2} />
<Column header="Profits" colSpan={2} />
</Row>
<Row>
<Column header="Last Year" />
<Column header="This Year" />
<Column header="Last Year" />
<Column header="This Year" />
</Row>
</ColumnGroup>
);
const footerGroup = (
<ColumnGroup>
<Row>
<Column footer="Totals:" colSpan={3} />
<Column footer="$506,202" />
<Column footer="$531,020" />
</Row>
</ColumnGroup>
);
return (
<TreeTable value={nodes} headerColumnGroup={headerGroup} footerColumnGroup={footerGroup}>
<Column field="brand" expander />
<Column field="lastYearSale" />
<Column field="thisYearSale" />
<Column field="lastYearProfit" />
<Column field="thisYearProfit" />
</TreeTable>
)
}
Pagination
Pagination is enabled by setting paginator property to true, rows property defines the number of rows per page and optionally pageLinks specify the the number of page links to display. See paginator component for more information about further customization options such as paginatorTemplate.
Pagination can either be used in Controlled or Uncontrolled manner. In controlled mode, first and onPage properties need to be defined to control the paginator state.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
export const TreeTablePageDemo = () => {
const [nodes, setNodes] = useState([]);
const [first, setFirst] = useState(0);
useEffect(() => {
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
return (
<TreeTable value={nodes} paginator rows={10}
first={first} onPage={e => setFirst(e.first)}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
)
}
In uncontrolled mode, only paginator and rows need to be enabled. Index of the first record can be still be provided using the first property in uncontrolled mode however it is evaluated at initial rendering and ignored in further updates. If you programmatically need to update the paginator state, prefer to use the component as controlled.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
export const TreeTablePageDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
return (
<TreeTable value={nodes} paginator rows={10}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
)
}
Elements of the paginator can be customized using the paginatorTemplate by the TreeTable. Refer to the template section of the paginator documentation for further options.
<TreeTable value={nodes} paginator rows={10}
paginatorTemplate="RowsPerPageDropdown PageLinks FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink">
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
Sorting
Enabling sortable property at column component would be enough to make a column sortable. The property to use when sorting is field by default and can be customized using sortField.
<Column field="name" header="Name" sortable />
By default sorting is executed on the clicked column only. To enable multiple field sorting, set sortMode property to "multiple" and use metakey when clicking on another column.
<TreeTable value={nodes} sortMode="multiple">
In case you'd like to display the table as sorted per a single column by default on mount, use sortField and sortOrder properties in Controlled or Uncontrolled manner. In controlled mode, sortField, sortOrder and onSort properties need to be defined to control the sorting state.
<TreeTable value={nodes} sortField={sortField} sortOrder={sortOrder} onSort={(e) => {setSortField(e.sortField); setSortOrder(e.sortOrder}}>
<Column field="name" header="Name" expander sortable></Column>
<Column field="size" header="Size" sortable></Column>
<Column field="type" header="Type" sortable></Column>
</TreeTable>
In multiple mode, use the multiSortMeta property and bind an array of SortMeta objects instead.
<TreeTable value={nodes} multiSortMeta={multiSortMeta} onSort={(e) => setMultiSortMeta(e.multiSortMeta)}>
<Column field="name" header="Name" expander sortable></Column>
<Column field="size" header="Size" sortable></Column>
<Column field="type" header="Type" sortable></Column>
</TreeTable>
let multiSortMeta = [];
multiSortMeta.push({field: 'year', order: 1});
multiSortMeta.push({field: 'brand', order: -1});
In uncontrolled mode, no additional properties need to be enabled. Initial sort field can be still be provided using the sortField property in uncontrolled mode however it is evaluated at initial rendering and ignored in further updates. If you programmatically need to update the sorting state, prefer to use the component as controlled.
<TreeTable value={nodes} sortField="year">
<Column field="name" header="Name" expander sortable></Column>
<Column field="size" header="Size" sortable></Column>
<Column field="type" header="Type" sortable></Column>
</TreeTable>
To customize sorting algorithm, set sortable option to custom and define a sortFunction that sorts the list.
<TreeTable value={nodes} sortField="year">
<Column field="name" header="Name" expander sortable></Column>
<Column field="size" header="Size" sortable="custom" sortFunction={mysort}></Column>
<Column field="type" header="Type" sortable></Column>
</TreeTable>
mysort(event) {
//event.field = Field to sort
//event.order = Sort order
}
Filtering
Filtering is enabled by setting the filter property on a column. filterMode specifies the filtering strategy. In lenient mode when the query matches a node, children of the node are not searched further as all descendants of the node are included. On the other hand, in strict mode when the query matches a node, filtering continues on all descendants.
<TreeTable value={nodes}>
<Column field="name" header="Name" expander filter></Column>
<Column field="size" header="Size" filter></Column>
<Column field="type" header="Type" filter></Column>
</TreeTable>
An optional global filter feature is available to search all fields with the same keyword, to implement this place an input component whose value is bound to the globalFilter property of the TreeTable.
export const TreeTableFilterDemo = () => {
const [nodes, setNodes] = useState{[]};
const [glogalFilter, setGlobalFilter] = useState(null);
useEffect( () => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
let header = <div style={{'textAlign':'left'}}>
<i className="pi pi-search" style={{margin:'4px 4px 0 0'}}></i>
<InputText type="search" onInput={(e) => setGlobalFilter(e.target.value)} placeholder="Global Search" size="50"/>
</div>;
return (
<TreeTable value={nodes} globalFilter={globalFilter} header={header}>
<Column field="name" header="Name" expander filter></Column>
<Column field="size" header="Size" filter></Column>
<Column field="type" header="Type" filter></Column>
</TreeTable>
)
}
In case you'd like to display the table as filtered by default on mount, use filters property in Controlled or Uncontrolled manner. In controlled mode, filters and onFilter properties need to be defined to control the filtering state.
export const TreeTableDefaultFilteredDemo = () => {
const [nodes, setNodes] = useState([]);
const [filters, setFilters] = useState(
'label': {
value: 'Events'
}
);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
render() {
return (
<TreeTable value={nodes} filters={filters} onFilter={(e) => setFilters(e.filters)}>
<Column field="name" header="Name" expander filter></Column>
<Column field="size" header="Size" filter></Column>
<Column field="type" header="Type" filter></Column>
</TreeTable>
);
}
}
In uncontrolled filtering, no additional properties need to be enabled. Initial filtering can be still be provided using the filters property in uncontrolled mode however it is evaluated at initial rendering and ignored in further updates. If you programmatically need to update the filtering state, prefer to use the component as controlled.
Custom filtering is implemented by setting the filterMatchMode property as "custom" and providing a function that takes the data value along with the filter value to return a boolean.
export const TreeTableFilterDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
const sizeFilter = (value, filter) => {
return filter > value;
}
render() {
return (
<TreeTable value={nodes}>
<Column field="name" header="Name" expander filter></Column>
<Column field="size" header="Size" filter filterMatchMode="custom" filterFunction={sizeFilter}></Column>
<Column field="type" header="Type" filter></Column>
</TreeTable>
);
}
}
Selection
TreeTable supports single, multiple and checkbox selection modes. Define selectionMode, selectionKeys and onSelectionChange properties to control the selection. In single mode, selectionKeys should be a single value whereas in multiple or checkbox modes an array is required. By default in multiple selection mode, metaKey is necessary to add to existing selections however this can be configured with metaKeySelection property. Note that in touch enabled devices, TreeTable does not require metaKey.
Example below demonstrates all cases along with the available callbacks to listen events such as node selection.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { Toast } from 'primereact/toast';
import { NodeService } from '../service/NodeService';
export const TreeTableSelectionDemo = () => {
const [nodes1, setNodes1] = useState([]);
const [nodes2, setNodes2] = useState([]);
const [nodes3, setNodes3] = useState([]);
const [nodes4, setNodes4] = useState([]);
const [nodes5, setNodes5] = useState([]);
const [selectedNodekey1, setSelectedNodekey1] = useState(null);
const [selectedNodekey2, setSelectedNodekey2] = useState(null);
const [selectedNodeKeys1, setSelectedNodeKeys1] = useState([]);
const [selectedNodeKeys2, setSelectedNodeKeys2] = useState([]);
const [selectedNodeKeys3, setSelectedNodeKeys3] = useState([]);
const toast = useRef(null);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes1(data));
nodeservice.getTreeTableNodes().then(data => setNodes2(data));
nodeservice.getTreeTableNodes().then(data => setNodes3(data));
nodeservice.getTreeTableNodes().then(data => setNodes4(data));
nodeservice.getTreeTableNodes().then(data => setNodes5(data));
}, [])
const onSelect = (event) => {
toast.current.show({severity: 'info', summary: 'Node Selected', detail: event.node.data.name});
}
onUnselect = (event) => {
toast.current.show({severity: 'info', summary: 'Node Unselected', detail: event.node.data.name});
}
return (
<div>
<Toast ref={toast} />
<h3 className="first">Single</h5>
<TreeTable value={nodes1} selectionMode="single" selectionKeys={selectedNodeKey1} onSelectionChange={e => setSelectedNodeKey1(e.value)}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Multiple</h5>
<TreeTable value={nodes2} selectionMode="multiple" selectionKeys={selectedNodeKeys1} onSelectionChange={e => setSelectedNodeKeys1(e.value)} metaKeySelection={false}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Multiple with MetaKey</h5>
<TreeTable value={nodes3} selectionMode="multiple" selectionKeys={selectedNodeKeys2} onSelectionChange={e => setSelectedNodeKeys2(e.value)} metaKeySelection>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Events</h5>
<TreeTable value={nodes4} selectionMode="single" selectionKeys={selectedNodeKey2} onSelectionChange={e => setSelectedNodeKey2(e.value)}
onSelect={onSelect} onUnselect={onUnselect}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Checkbox</h5>
<TreeTable value={nodes5} selectionMode="checkbox" selectionKeys={selectedNodeKeys3} onSelectionChange={e => setSelectedNodeKeys3(e.value)}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
</div>
)
}
Lazy
Lazy loading is implemented using the onExpand event by adding children to the expanded node. leaf property should be enabled to indicate the node has children but not yet loaded. Here is a in-memory demo that loads generated nodes on expand event to imitate a remote call with a timeout. Notice the usage of loading property as well to give users a feedback about the loading process.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
export const TreeTableLazyDemo = () => {
const [nodes, setNodes] = useState([]);
const [first, setFirst] = useState(0);
const [rows, setRows] = useState(10);
const [totalRecords, setTotalRecords] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
setNodes(loadNodes(first, first + rows));
setTotalRecords(1000);
}, 1000);
}, []);
const loadNodes = (start, end) => {
let nodes = [];
for(let i = start; i < end; i++) {
let node = {
key: i,
data: {
name: 'Item ' + (start + i),
size: Math.floor(Math.random() * 1000) + 1 + 'kb',
type: 'Type ' + (start + i)
},
leaf: false
};
nodes.push(node);
}
return nodes;
}
const onExpand = (event) => {
if (!event.node.children) {
setLoading(true);
setTimeout(() => {
setLoading(false);
let lazyNode = {...event.node};
lazyNode.children = [
{
data: {
name: lazyNode.data.name + ' - 0',
size: Math.floor(Math.random() * 1000) + 1 + 'kb',
type: 'File'
},
},
{
data: {
name: lazyNode.data.name + ' - 1',
size: Math.floor(Math.random() * 1000) + 1 + 'kb',
type: 'File'
}
}
];
let nodes = [...nodes];
nodes[event.node.key] = lazyNode;
setLoading(false);
setNodes(nodes)
}, 250);
}
}
const onPage = (event) => {
setLoading(true)
//imitate delay of a backend call
setTimeout(() => {
setFirst(event.first);
setRows(event.rows);
setNodes(loadNodes(event.first, event.first + event.rows));
setLoading(false);
}, 1000);
}
return (
<TreeTable value={nodes} lazy paginator totalRecords={totalRecords}
first={first} rows={rows} onPage={onPage} onExpand={onExpand} loading={loading}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
)
}
Incell Editing
Incell editing feature provides a way to quickly edit data inside the table. A cell editor is defined using the editor property that refers to a function to return an input element for the editing. Clicking outside the cell or hitting enter key closes the cell, however this may not be desirable if the input is invalid. In order to decide whether to keep the cell open or not, provide a editorValidator function that validates the value.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { InputText } from 'primereact/inputtext;
import { NodeService } from '../service/NodeService';
export const TreeTableEditDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
const onEditorValueChange = (props, value) => {
let newNodes = JSON.parse(JSON.stringify(nodes));
let editedNode = findNodeByKey(newNodes, props.node.key);
editedNode.data[props.field] = value;
setNodes(newNodes)
}
const findNodeByKey = (nodes, key) => {
let path = key.split('-');
let node;
while (path.length) {
let list = node ? node.children : nodes;
node = list[parseInt(path[0], 10)];
path.shift();
}
return node;
}
const inputTextEditor = (props, field, width) => {
return (
<InputText type="text" value={props.node.data[field]} style={{'width': width, 'padding': 0}}
onChange={(e) => onEditorValueChange(props, e.target.value)} />
);
}
const sizeEditor = (props) => {
return inputTextEditor(props, 'size', '100%');
}
const typeEditor = (props) => {
return inputTextEditor(props, 'type', '100%');
}
const requiredValidator = (e) => {
let props = e.columnProps;
let value = props.node.data[props.field];
return value && value.length > 0;
}
return (
<TreeTable value={nodes}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size" editor={sizeEditor} editorValidator={requiredValidator}></Column>
<Column field="type" header="Type" editor={typeEditor}></Column>
</TreeTable>
)
}
ContextMenu
One or more ContextMenu instances can be attached to nodes. Similar to selection, separate contextMenuSelectionKey and onContextMenuSelectionChange properties are necesary to manage the selected node with right click. In addition, a context menu can either be displayed at onContextMenu event. Since this event also passes the node instance, you may choose to display a different context menu for a particular node.
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { ContextMenu } from 'primereact/contextmenu';
import { Toast } from 'primereact/toast';
import { NodeService } from '../service/NodeService';
export const TreeTableContextMenuDemo = () => {
const [nodes, setNodes] = useState([]);
const [expandedKeys, setExpandedKeys] = useState({});
const [selectedNodeKey, setSelectedNodeKey] = useState(null);
const toast = useRef(null);
const cm = useRef(null);
const menu = [
{
label: 'View Key',
icon: 'pi pi-search',
command: () => {
toast.current.show({severity: 'success', summary: 'Node Key', detail: selectedNodeKey});
}
},
{
label: 'Toggle',
icon: 'pi pi-cog',
command: () => {
let expandedKeys = {...expandedKeys};
if (expandedKeys[selectedNodeKey])
delete expandedKeys[selectedNodeKey];
else
expandedKeys[selectedNodeKey] = true;
setExpandedKeys(expandedKeys);
}
}
];
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
return (
<div>
<Toast ref={toast} />
<ContextMenu model={menu} ref={cm} onHide={() => setSelectedNodeKey(null)}/>
<TreeTable value={nodes} expandedKeys={expandedKeys} onToggle={e => setExpandedKeys(e.value)}
contextMenuSelectionKey={selectedNodeKey} onContextMenuSelectionChange={event => setSelectedNodeKey(e.value)}
onContextMenu={event => cm.current.show(event.originalEvent)}>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
</div>
)
}
Column Resize
Columns can be resized using drag drop by setting the resizableColumns to true. There are two resize modes; "fit" and "expand". Fit is the default one and the overall table width does not change when a column is resized. In "expand" mode, table width also changes along with the column width. onColumnResizeEnd is a callback that passes the resized column header as a parameter.
<h5>Fit Mode</h5>
<TreeTable value={nodes} resizableColumns columnResizeMode="fit">
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
<h5>Expand Mode</h5>
<TreeTable value={nodes} resizableColumns columnResizeMode="expand">
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
It is important to note that when you need to change column widths, since table width is 100%, giving fixed pixel widths does not work well as browsers scale them, instead give percentage widths.
<TreeTable value={nodes} resizableColumns>
<Column field="name" header="Name" expander style={{width:'50%'}}></Column>
<Column field="size" header="Size" style={{width:'30%'}}></Column>
<Column field="type" header="Type" style={{width:'20%'}}></Column>
</TreeTable>
Column Reorder
Columns can be reordered using drag drop by setting the reorderableColumns to true. onColReorder is a callback that is invoked when a column is reordered. TreeTable keeps the column order state internally using keys that identifies a column using the field property. If the column has no field, use columnKey instead.
<TreeTable value={nodes} reorderableColumns>
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
Scrolling
TreeTable supports both horizontal and vertical scrolling as well as frozen columns. Vertical scrolling is enabled using scrollable property and scrollHeight to define the viewport height.
<TreeTable value={nodes} scrollable scrollHeight="200px">
<Column field="name" header="Name" expander></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
Horizontal Scrolling requires a width of DataTable to be defined and explicit widths on columns.
<TreeTable value={nodes} scrollable style={{width: '600px'}}>
<Column field="name" header="Name" expander style={{width:'350px'}}></Column>
<Column field="size" header="Size" style={{width:'350px'}}></Column>
<Column field="type" header="Type" style={{width:'350px'}}></Column>
</TreeTable>
Certain columns can be frozen by using the frozen property of the column component. Widths of the frozen section is specified by the frozenWidth property.
<TreeTable value={nodes} scrollable frozenWidth="200px" scrollHeight="250px">
<Column field="name" header="Name" expander frozen style={{width:'250px'}}></Column>
<Column field="size" header="Size" style={{width:'250px'}}></Column>
<Column field="type" header="Type" style={{width:'250px'}}></Column>
<Column field="size" header="Size" style={{width:'250px'}}></Column>
<Column field="type" header="Type" style={{width:'250px'}}></Column>
<Column field="size" header="Size" style={{width:'250px'}}></Column>
<Column field="type" header="Type" style={{width:'250px'}}></Column>
</TreeTable>
Note that frozen columns are enabled, frozen and scrollable cells may have content with varying height which leads to misalignment. Provide fixed height to cells to avoid alignment issues.
<TreeTable value={nodes} scrollable frozenWidth="200px" scrollHeight="250px">
<Column field="name" header="Name" expander frozen style={{width:'250px', height: '25px'}}></Column>
<Column field="size" header="Size" style={{width:'250px', height: '25px'}}></Column>
<Column field="type" header="Type" style={{width:'250px', height: '25px'}}></Column>
</TreeTable>
When using frozen columns with column grouping, use frozenHeaderColumnGroup and frozenFooterColumnGroup properties along with headerColumnGroup and footerColumnGroup.
Responsive
TreeTable columns are displayed as stacked in responsive mode if the screen size becomes smaller than a certain breakpoint value. Here is a sample implementation;
.p-col-d {
display: table-cell;
}
.p-col-m {
display: none;
}
@media screen and (max-width: 64em) {
.p-col-d {
display: none;
}
.p-col-m {
display: inline-block;
}
}
import React, { Component } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { NodeService } from '../service/NodeService';
export const TreeTableResponsiveDemo = () => {
const [nodes, setNodes] = useState([]);
useEffect(() => {
nodeservice = new NodeService();
nodeservice.getTreeTableNodes().then(data => setNodes(data));
}, [])
const nameTemplate = (node) => {
return (
<React.Fragment>
<span>{node.data.name}</span>
<span className="p-col-m">, {node.data.size}</span>
<span className="p-col-m">, {node.data.type}</span>
</React.Fragment>
)
}
return (
<TreeTable value={nodes} header="Responsive TreeTable">
<Column field="name" header="Name" body={nameTemplate} expander headerClassName="p-col-d"></Column>
<Column field="size" header="Size" className="p-col-d"></Column>
<Column field="type" header="Type" className="p-col-d"></Column>
</TreeTable>
)
}
Theming
TreeTable supports various themes featuring Material, Bootstrap, Fluent as well as your own custom themes via the Designer tool.
Resources
Visit the PrimeReact TreeTable showcase for demos and documentation.