
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@florianreihl/react-datatable
Advanced tools
A powerful, flexible React component for to display tabular json data with built-in sorting, pagination, filtering, and metadata support.
A powerful, flexible React component for displaying tabular data with built-in sorting, pagination, filtering, and rich metadata support.
npm install @florianreihl/react-datatable
yarn add @florianreihl/react-datatable
import React from 'react';
import { DataTable } from '@florianreihl/react-datatable';
const columns = [
{
name: 'id',
label: 'ID',
dataType: 'integer',
keySequence: 1
},
{
name: 'name',
label: 'Full Name',
dataType: 'text'
},
{
name: 'email',
label: 'Email Address',
dataType: 'text'
},
{
name: 'age',
label: 'Age',
dataType: 'integer'
}
];
const data = [
[1, 'John Doe', 'john@example.com', 28],
[2, 'Jane Smith', 'jane@example.com', 32],
[3, 'Bob Johnson', 'bob@example.com', 45],
[4, 'Alice Brown', 'alice@example.com', 29],
[5, 'Charlie Wilson', 'charlie@example.com', 38]
];
function App() {
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">My Data Table</h1>
<DataTable
data={data}
columns={columns}
height="400px"
showRowNumbers={true}
showTooltips={true}
/>
</div>
);
}
export default App;
import React, { useState } from 'react';
import { DataTable, getPaginationInfo } from '@florianreihl/react-datatable';
function AdvancedTable() {
const [currentPage, setCurrentPage] = useState(1);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [sortColumns, setSortColumns] = useState([]);
const handleSort = (columnName) => {
setSortColumns(prevSort => {
const existingIndex = prevSort.findIndex(sort => sort.column === columnName);
if (existingIndex === -1) {
// Add new sort
return [...prevSort, { column: columnName, direction: 'asc' }];
} else {
// Toggle existing sort
const existing = prevSort[existingIndex];
if (existing.direction === 'asc') {
return prevSort.map((sort, index) =>
index === existingIndex ? { ...sort, direction: 'desc' } : sort
);
} else {
// Remove sort
return prevSort.filter((_, index) => index !== existingIndex);
}
}
});
};
const paginationInfo = getPaginationInfo(currentPage, rowsPerPage, data.length);
return (
<div>
<DataTable
data={data}
columns={columns}
currentPage={currentPage}
rowsPerPage={rowsPerPage}
sortColumns={sortColumns}
onSort={handleSort}
height="500px"
/>
{/* Pagination Controls */}
<div className="flex justify-between items-center mt-4">
<div className="text-sm text-gray-600">
Showing {paginationInfo.startIndex + 1} to {paginationInfo.endIndex} of {data.length} rows
</div>
<div className="flex space-x-2">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={!paginationInfo.hasPrevPage}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Previous
</button>
<span className="px-3 py-1">
Page {currentPage} of {paginationInfo.totalPages}
</span>
<button
onClick={() => setCurrentPage(prev => prev + 1)}
disabled={!paginationInfo.hasNextPage}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Next
</button>
</div>
</div>
</div>
);
}
const customCellRenderer = (value, column, row, rowIndex) => {
if (column.name === 'email') {
return (
<a href={`mailto:${value}`} className="text-blue-600 hover:underline">
{value}
</a>
);
}
if (column.name === 'age') {
const age = parseInt(value);
if (age >= 40) {
return <span className="font-bold text-red-600">{value}</span>;
}
if (age >= 30) {
return <span className="font-medium text-orange-600">{value}</span>;
}
return <span className="text-green-600">{value}</span>;
}
return value;
};
<DataTable
data={data}
columns={columns}
cellRenderer={customCellRenderer}
onRowClick={(row, index) => console.log('Row clicked:', row)}
onCellClick={(value, column, row, index) => console.log('Cell clicked:', value)}
/>
function TableWithColumnControl() {
const [visibleColumns, setVisibleColumns] = useState(['id', 'name', 'email']);
const [isLabelToggled, setIsLabelToggled] = useState(false);
return (
<div>
{/* Column Controls */}
<div className="mb-4 p-4 bg-gray-100 rounded">
<h3 className="font-medium mb-2">Column Visibility:</h3>
<div className="flex flex-wrap gap-2 mb-3">
{columns.map(column => (
<label key={column.name} className="flex items-center">
<input
type="checkbox"
checked={visibleColumns.includes(column.name)}
onChange={(e) => {
if (e.target.checked) {
setVisibleColumns(prev => [...prev, column.name]);
} else {
setVisibleColumns(prev => prev.filter(name => name !== column.name));
}
}}
className="mr-1"
/>
{column.name}
</label>
))}
</div>
<label className="flex items-center">
<input
type="checkbox"
checked={isLabelToggled}
onChange={(e) => setIsLabelToggled(e.target.checked)}
className="mr-2"
/>
Show column labels instead of names
</label>
</div>
<DataTable
data={data}
columns={columns}
visibleColumns={visibleColumns}
isLabelToggled={isLabelToggled}
height="400px"
/>
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
data | any[][] | [] | Array of rows, where each row is an array of values |
columns | Column[] | [] | Array of column definitions |
currentPage | number | 1 | Current page number |
rowsPerPage | number | 25 | Number of rows per page |
visibleColumns | string[] | null | null | Array of column names to show (null = show all) |
showRowNumbers | boolean | true | Show row numbers column |
isLabelToggled | boolean | false | Show labels instead of column names |
showTooltips | boolean | true | Show metadata tooltips on hover |
height | string | "400px" | Table height |
className | string | "" | Additional CSS classes |
headerIcon | string | ReactNode | null | Icon for row numbers header |
sortColumns | SortColumn[] | [] | Current sort configuration |
onSort | function | null | Called when column header is clicked |
onRowClick | function | null | Called when row is clicked |
onCellClick | function | null | Called when cell is clicked |
processedData | any[][] | null | Pre-filtered/sorted data (overrides data) |
cellRenderer | function | null | Custom cell renderer |
emptyStateRenderer | function | null | Custom empty state component |
loading | boolean | false | Show loading state |
loadingRenderer | function | null | Custom loading component |
interface Column {
name: string; // Unique column identifier
label?: string; // Human-readable label
dataType: string; // Data type (text, integer, float, etc.)
itemOID?: string; // Item OID for metadata
keySequence?: number; // Key sequence for primary keys
length?: number; // Maximum length
codelist?: string; // Codelist reference
format?: string; // Display format
significantDigits?: number; // Significant digits for numbers
}
interface SortColumn {
column: string; // Column name to sort by
direction: 'asc' | 'desc'; // Sort direction
}
getPaginationInfo(currentPage, rowsPerPage, totalRows)Returns detailed pagination information:
interface PaginationInfo {
totalPages: number;
startIndex: number;
endIndex: number;
isFirstPage: boolean;
isLastPage: boolean;
hasNextPage: boolean;
hasPrevPage: boolean;
}
calculateTotalPages(totalRows, rowsPerPage)Returns the total number of pages for pagination.
This component uses Tailwind CSS classes. Make sure Tailwind CSS is available in your project. If you're not using Tailwind, you can override the styles with your own CSS.
Key CSS classes used:
bg-white, border-gray-300 - Basic stylinghover:bg-gray-50 - Row hover effectstext-purple-600 - Sort and key indicatorssticky, top-0 - Fixed headerCheck out the examples directory for more detailed usage examples including:
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/AmazingFeature)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)This project is licensed under the MIT License - see the LICENSE file for details.
Florian Reihl
Made with ❤️ by Florian Reihl
FAQs
A powerful, flexible React component for to display tabular json data with built-in sorting, pagination, filtering, and metadata support.
We found that @florianreihl/react-datatable demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.