
At CloudAnswers, we use the react-data-table-component for managing data in tables. It is quite good when it comes to pagination, sorting, and other important features. However, there were missing features in the original component, so we decided to add some functionality that we needed on top of it.
In this article, you’ll see how you can enhance the features and usability of the react-data-table-component
as we did for some of our customer applications.
Expanding All Rows
In the original react-data-table-component repository, you can select all the rows at once with the “Select All” checkbox. However, you cannot expand all the rows if you want. To solve this issue, we created a fork of the original repository and edited its source code to allow users to expand/collapse all the rows at the same time.
If you want to use our fork, you can use the following props to configure the behavior for expandable rows:
keepExpandableFirst
— By default, the select checkbox is shown before the expand button. So, in case, you want to reverse their order, you can use the keepExpandableFirst prop.expandableRowsNoExpandAll
— In case, you want to disable the “Expand All” button, you can pass theexpandableRowsNoExpandAll
prop to do so.
Adding Footer Component
The original react-data-table-component
provides support for adding a header but not a footer. A footer is often used in a table to summarise the data in the table, for example, you can add a “totals” row in the footer if your table contains some numeric data. So, we added a footer
prop to which you can pass any functional component.
If you want to use our fork of the original repository, you can use the footer in the following manner:
<ReactDataTable
...
footer={
<ReactDataTable
...
/>
}
/>
Configuring Table Columns
As the application’s business complexity increases, the fields/columns in the tables increase. Due to this, the UI becomes a little more clunky and difficult to interpret. One solution for this problem is to allow users to hide/display columns in the tables based on their own needs.
For this purpose, you can create a component called TableColumnsConfigurator
with the following code:
const TableColumnsConfigurator = ({
columns,
hiddenColumns,
setHiddenColumns,
className,
}) => {
const columnsWithValidIDs = columns.filter(
(column) => column.id && typeof column.id === "string"
);
const isColumnHidden = (column) => {
return column.id && hiddenColumns.includes(column.id);
};
const updateHiddenColumns = (column) => {
setHiddenColumns((prevValue) => {
let _hiddenColumns = [...prevValue];
if (column.id) {
if (_hiddenColumns.includes(column.id)) {
_hiddenColumns = _hiddenColumns.filter(
(_hiddenColumn) => _hiddenColumn !== column.id
);
} else {
_hiddenColumns = [..._hiddenColumns, column.id];
}
}
return _hiddenColumns;
});
};
const clearHiddenColumns = () => {
setHiddenColumns([]);
};
const id = useUniqueId();
return (
<div className={cx("d-flex", className)}>
<Dropdown alignRight>
<OverlayTrigger
overlay={
<Tooltip id={id}>
Edit Columns{" "}
{hiddenColumns.length > 0 && (
<div className="small">
({hiddenColumns.length} hidden column
{hiddenColumns.length > 1 && "s"})
</div>
)}
</Tooltip>
}
>
<Dropdown.Toggle variant="transparent" className="p-0 text-muted">
<i className="fas fa-list" />
{hiddenColumns.length > 0 && (
<div className="visual-indicator-danger" />
)}
</Dropdown.Toggle>
</OverlayTrigger>
<Dropdown.Menu className="py-0" style={{ minWidth: "200px" }}>
{columnsWithValidIDs.length > 0 ? (
<>
<div className="dropdown-header d-flex justify-content-between border-bottom p-h">
<div className="font-weight-bold">Edit Columns</div>
<Button
variant="link"
className="p-0"
onClick={clearHiddenColumns}
>
Show All
</Button>
</div>
<div style={{ maxHeight: "300px", overflowY: "auto" }}>
{columnsWithValidIDs.map((column, index) => (
<div
key={column.id}
className="dropdown-item d-flex justify-content-between px-h py-q"
style={{ opacity: isColumnHidden(column) ? 0.5 : 1 }}
>
<span className="pr-h">{column.name}</span>
<input
type="checkbox"
checked={!isColumnHidden(column)}
onClick={() => updateHiddenColumns(column)}
readOnly
/>
</div>
))}
</div>
</>
) : (
<div className="px-h">No columns available to show/hide.</div>
)}
</Dropdown.Menu>
</Dropdown>
</div>
);
};
Next, you can call the TableColumnsConfigurator
component in a sub-header component and pass this to the subHeaderComponent
prop in the ReactDataTable
:
const subHeaderComponent = () => {
return (
<div className="mb-0">
{headerComponent}
<div className="d-flex justify-content-end align-items-center py-q">
<div className="d-flex">
<TableColumnsConfigurator
columns={columns}
hiddenColumns={hiddenColumns}
setHiddenColumns={setHiddenColumns}
/>
</div>
</div>
</div>
);
};
Exporting to CSV and PDF
A common request from users of business applications is the ability to export the data to CSV and PDF formats.
For this purpose, you can create a component called ExportDataDropdown
with the following code:
const ExportDataDropdown = ({ columns, hiddenColumns, data }) => {
const [fileName, setFileName] = useState(
`report-${new Date().toLocaleDateString()}`
);
const csvFileName = useMemo(() => `${fileName}.csv`, [fileName]);
const pdfFileName = useMemo(() => `${fileName}.pdf`, [fileName]);
const handleFileNameChange = (e) => setFileName(e.target.value);
const viewColumns = useMemo(
() => columns.filter((column) => !hiddenColumns.includes(column.id ?? "")),
[columns, hiddenColumns]
);
const handleExportToCSV = useCallback(() => {
const headers = viewColumns.map((column) => column.name);
const csvData = data.map((row) =>
viewColumns.map((column) => {
return column.selector?.(row) ?? "";
})
);
// write your logic for CSV export
exportCsv({
name: csvFileName,
headers,
body: csvData,
});
}, [csvFileName, data, viewColumns]);
const handleExportToPDF = useCallback(() => {
const headers = viewColumns.map((column) => column.name);
const pdfData = data.map((row, rowIndex) =>
viewColumns.map((column, colIndex) => {
return column.selector?.(row) ?? "";
})
);
// write your logic for PDF export
exportPdf({
name: pdfFileName,
headers,
body: pdfData,
});
}, [data, pdfFileName, viewColumns]);
return (
<Dropdown alignRight>
<Dropdown.Toggle variant="transparent" className="p-0 text-muted">
Export
</Dropdown.Toggle>
<Dropdown.Menu className="p-h" style={{ minWidth: "200px" }}>
<div className="d-flex flex-column">
<Form.Group className="mb-h">
<Form.Control
type="text"
placeholder="File Name"
value={fileName}
onChange={handleFileNameChange}
/>
<Form.Text className="text-muted">
Enter file name without file extension
</Form.Text>
</Form.Group>
<DropdownButton
as={ButtonGroup}
variant="outline-primary"
title="Export As"
alignRight
>
<Dropdown.Item onClick={handleExportToPDF}>
Export as PDF
</Dropdown.Item>
<Dropdown.Item onClick={handleExportToCSV}>
Export as CSV
</Dropdown.Item>
</DropdownButton>
</div>
</Dropdown.Menu>
</Dropdown>
);
};
Next, you can call the ExportDataDropdown
component in a sub-header component and pass this to the subHeaderComponent
prop in the ReactDataTable
:
const subHeaderComponent = () => {
return (
<div className="mb-0">
{headerComponent}
<div className="d-flex justify-content-end align-items-center py-q">
<div className="d-flex">
<ExportDataDropdown
columns={columns}
hiddenColumns={hiddenColumns}
data={data}
/>
</div>
</div>
</div>
);
};
Persisting User Settings
When your invest time in customizing columns and filters, you will expect the application to remember your preferences on page reload or when you return to the application later in the day.
This can easily be accomplished by storing user settings in the browser’s local storage. You can replace the use of the useState
hook with the useLocalStorage
custom hook. It works in the same way as useState
works but allows you to save your state variables in local storage.
For example, you might want to persist the filename used in the ExportDataDropdown
component in the local storage. For this, you can use the following code:
const ExportDataDropdown = () => {
const [fileName, setFileName] = useLocalStorage('fileName', `report-${new Date().toLocaleDateString()}`);
...
};
The first parameter is the “key” and the second parameter is the default value in case the key doesn’t exist in local storage.
Conclusion
As a developer, there is always the temptation to create a new component from scratch, but extending an existing component that already solves 90% of your needs is a far more efficient approach. Here we are standing on the shoulders of giants, by starting with the react-data-table component. We’ve opened a pull request for them to include these features but if you want to use our version today, you can find it at https://github.com/cloudanswers/react-data-table-component.
Related Articles

AppExchange Review Series
As a long time Salesforce partner (since 2008), on both the consulting and AppExchange side, we realized we have a wealth of experience in building, installing, configuring, and using AppExchange apps. Our consulting customers often come to us with questions about which app would solve a particular problem, and as part of our AppExchange business we are often trying apps to learn what to do (and not to do) when building our own.
July 24, 2024
3 Min Read

Product Launch: CloudAnswers Shop Builder
Are you looking for an easy way to launch an ecommerce shop? Our new app, Shop Builder, is now in public beta! We’re looking for companies that want to build an online shop but don’t want to spend thousands building it out.
April 12, 2024
5 Min Read

A Day in the Life of a Project Manager at CloudAnswers
I'm Emily, and I've been a project manager at CloudAnswers for the last two years. It can be a nebulous role, but I like to say I act as a bridge between the product vision and tangible results, whether that is building a custom app for a client or one of our own Salesforce products. My typical day revolves around managing tasks, ensuring progress, and maintaining standards while adhering to project timelines.
March 22, 2024
5 Min Read