@tronstride/datatable
v0.1.3
Published
Server-driven React DataTable for TronStride apps — sorting, filtering, pagination, row selection, and expandable rows.
Readme
@tronstride/datatable
Internal server-driven React DataTable component for TronStride applications.
@tronstride/datatable is a reusable React DataTable component designed for TronStride applications. It provides a consistent table experience across internal products while supporting server-side data fetching, sorting, filtering, pagination, row selection, expandable nested rows, and refresh functionality.
Internal Package: This package is intended for use within TronStride applications and is not intended for public distribution.
Features
- Server-side pagination
- Server-side sorting
- Server-side filtering
- Row selection
- Expandable nested rows
- Server-driven nested data
- Refresh functionality
- React Query integration
- TanStack Table integration
- Mantine UI integration
- React Intl support
- Reusable column configuration
- Axios/request-client integration
- Configurable API response mapping
- Support for create/action controls
- Shared component for multiple TronStride applications
Installation
Install the package using pnpm:
pnpm add @tronstride/datatableIf the package is hosted in an internal npm registry, make sure your npm configuration is authenticated with the TronStride registry before installing.
Peer Dependencies
The consuming application should have the required peer dependencies installed.
pnpm add react react-dom @mantine/core @mantine/hooks @tanstack/react-query @tanstack/react-table @tabler/icons-react react-intlIf your application already contains these dependencies, you do not need to install them again.
Recommended: bridge your HTTP client with DataTableContext
Most TronStride apps already expose an Axios (or similar) client through an app-level context (for example HTTPContext). Bridge that client once, then every screen can use your local wrapper without passing request each time:
// src/components/DataTable.jsx (in your app)
import { useContext } from "react";
import {
DataTable as PackageDataTable,
DataTableContext,
} from "@tronstride/datatable";
import { HTTPContext } from "@/context/http";
export default function DataTable(props) {
const request = useContext(HTTPContext);
return (
<DataTableContext.Provider value={request}>
<PackageDataTable {...props} />
</DataTableContext.Provider>
);
}Then screens import your local wrapper:
import DataTable from "@/components/DataTable";
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request.get("/invoices", { params }).then((response) => response.data)
}
responseKey="items"
create={false}
refresh
/>queryFn still receives { request, params }. The request value comes from DataTableContext (or from an explicit request prop, which takes priority).
You can also read the client inside custom cells/hooks with useDataTableRequest().
Basic Usage (explicit request prop)
import { DataTable } from "@tronstride/datatable";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MantineProvider } from "@mantine/core";
import { IntlProvider } from "react-intl";
import "@mantine/core/styles.css";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<IntlProvider locale="en" messages={{}}>
<MantineProvider>
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request
.get("/invoices", { params })
.then((response) => response.data)
}
request={apiClient}
responseKey="items"
create={false}
refresh
/>
</MantineProvider>
</IntlProvider>
</QueryClientProvider>
);
}
export default App;DataTable Configuration
The DataTable is designed around server-driven data.
A typical configuration looks like this:
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request.get("/invoices", { params }).then((response) => response.data)
}
responseKey="items"
create={false}
refresh
/>Pass request either via the prop or via DataTableContext.Provider.
Configuration Overview
| Property / export | Description |
| ------------------- | --------------------------------------------------------------------------- |
| columns | Table column configuration |
| queryKey | Unique React Query key for the table |
| queryFn | Function responsible for fetching server data (({ request, params }) =>) |
| request | Optional HTTP client; falls back to DataTableContext |
| DataTableContext | React context — provide your app HTTP client once at a wrapper boundary |
| useDataTableRequest | Hook to read the context HTTP client |
| responseKey | Property containing the table rows |
| create | Controls create functionality |
| refresh | Enables table refresh functionality |
| expandable | Configuration for expandable nested rows |
Columns
Columns define how the table data is displayed.
Example:
const columns = [
{
accessorKey: "invoiceNumber",
header: "Invoice Number",
},
{
accessorKey: "customerName",
header: "Customer",
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "totalAmount",
header: "Total Amount",
},
];The column configuration is based on the TanStack Table column model supported by the component.
Server-Side Data Fetching
The DataTable expects the consuming application to provide a queryFn.
Example:
queryFn={({ request, params }) =>
request
.get('/invoices', { params })
.then((response) => response.data)
}The component provides the request parameters through params.
The application is responsible for sending these parameters to the backend API.
Expected API Response
The default API response should contain the table data and pagination metadata.
Example:
{
"items": [
{
"id": 1,
"invoiceNumber": "INV-001",
"customerName": "ABC Trading",
"status": "PAID",
"totalAmount": 1250
},
{
"id": 2,
"invoiceNumber": "INV-002",
"customerName": "XYZ LLC",
"status": "PENDING",
"totalAmount": 2500
}
],
"meta": {
"totalCount": 100
}
}responseKey
responseKey tells the DataTable which property contains the table rows.
For example:
responseKey = "items";expects the API response to contain:
{
"items": []
}If the backend returns a different property:
{
"data": []
}configure:
responseKey = "data";Pagination
Pagination is server-driven.
The DataTable sends the required pagination information through the params argument.
Example:
queryFn={({ request, params }) =>
request
.get('/invoices', {
params,
})
.then((response) => response.data)
}The backend should return the total number of available records through:
{
"meta": {
"totalCount": 100
}
}The DataTable uses totalCount to calculate the pagination state.
Sorting
Sorting is handled through the server-driven query mechanism.
When the user changes the sorting configuration, the DataTable updates the request parameters and triggers the query again.
Example:
queryFn={({ request, params }) =>
request
.get('/invoices', {
params,
})
.then((response) => response.data)
}The backend should read the sorting parameters and return the appropriately sorted records.
Filtering
The DataTable supports server-driven filtering.
Filter values are included in the request parameters passed to queryFn.
Example:
queryFn={({ request, params }) =>
request
.get('/invoices', {
params,
})
.then((response) => response.data)
}The backend should process the filter parameters and return the filtered dataset.
Refresh
Enable refresh functionality using:
refresh;Example:
<DataTable
columns={columns}
queryKey="invoices"
queryFn={queryFn}
request={apiClient}
responseKey="items"
refresh
/>This allows users to manually refresh the table data.
Row Selection
The DataTable supports row selection for use cases such as:
- Bulk actions
- Delete operations
- Export
- Status updates
- Approval workflows
- Batch processing
Example:
<DataTable
columns={columns}
queryKey="invoices"
queryFn={queryFn}
request={apiClient}
responseKey="items"
/>Selection behavior should be configured according to the supported DataTable API.
Expandable Rows
The DataTable supports expandable nested rows for displaying related server-side data.
A common example is displaying invoice lines under an invoice.
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request.get("/invoices", { params }).then((response) => response.data)
}
request={apiClient}
responseKey="items"
expandable={{
queryKey: "invoice-lines",
key: "lines",
parentParamKey: "eInvoiceEntryId",
nestedColumns,
queryFn: ({ request, params }) =>
request.get("/lines", { params }).then((response) => response.data),
}}
/>Expandable Configuration
| Property | Description |
| ---------------- | -------------------------------------------- |
| queryKey | React Query key used for nested data |
| key | Property/key associated with nested data |
| parentParamKey | Parameter used to identify the parent record |
| nestedColumns | Column configuration for nested rows |
| queryFn | Function used to fetch nested records |
Example: Invoice DataTable
const invoiceColumns = [
{
accessorKey: "invoiceNumber",
header: "Invoice Number",
},
{
accessorKey: "customerName",
header: "Customer",
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "totalAmount",
header: "Total Amount",
},
];
const invoiceLineColumns = [
{
accessorKey: "itemName",
header: "Item",
},
{
accessorKey: "quantity",
header: "Quantity",
},
{
accessorKey: "unitPrice",
header: "Unit Price",
},
{
accessorKey: "amount",
header: "Amount",
},
];
function InvoiceTable() {
return (
<DataTable
columns={invoiceColumns}
queryKey="invoices"
queryFn={({ request, params }) =>
request.get("/invoices", { params }).then((response) => response.data)
}
request={apiClient}
responseKey="items"
refresh
expandable={{
queryKey: "invoice-lines",
key: "lines",
parentParamKey: "eInvoiceEntryId",
nestedColumns: invoiceLineColumns,
queryFn: ({ request, params }) =>
request.get("/lines", { params }).then((response) => response.data),
}}
/>
);
}React Query
The DataTable uses TanStack React Query for server-side data management.
The consuming application should provide a QueryClientProvider.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<AppContent />
</QueryClientProvider>
);
}The DataTable should be rendered inside the provider.
Mantine
The DataTable uses Mantine components and requires the Mantine styles.
import { MantineProvider } from "@mantine/core";
import "@mantine/core/styles.css";Wrap the application with:
<MantineProvider>
<App />
</MantineProvider>React Intl
The DataTable uses react-intl for toolbar, pagination, and empty-state labels. Wrap the application with IntlProvider (messages can be empty — every string has a defaultMessage).
import { IntlProvider } from "react-intl";
<IntlProvider locale="en" messages={{}}>
<App />
</IntlProvider>For applications supporting multiple languages, provide the appropriate locale and messages.
Request Client
Provide your existing HTTP client in either of these ways:
- Context (recommended for apps) — wrap once with
DataTableContext.Provider - Prop — pass
request={apiClient}on a specific table (overrides context)
For example, using Axios:
import axios from "axios";
const apiClient = axios.create({
baseURL: "/api",
});import { DataTable, DataTableContext } from "@tronstride/datatable";
<DataTableContext.Provider value={apiClient}>
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request.get("/invoices", { params }).then((response) => response.data)
}
responseKey="items"
/>
</DataTableContext.Provider>This allows the application to keep its existing:
- Authentication
- Base URL
- Headers
- Interceptors
- Error handling
- Token refresh logic
Complete Example
import { DataTable } from "@tronstride/datatable";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MantineProvider } from "@mantine/core";
import "@mantine/core/styles.css";
const queryClient = new QueryClient();
const columns = [
{
accessorKey: "invoiceNumber",
header: "Invoice Number",
},
{
accessorKey: "customerName",
header: "Customer",
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "totalAmount",
header: "Total Amount",
},
];
const nestedColumns = [
{
accessorKey: "itemName",
header: "Item",
},
{
accessorKey: "quantity",
header: "Quantity",
},
{
accessorKey: "amount",
header: "Amount",
},
];
function App() {
return (
<QueryClientProvider client={queryClient}>
<MantineProvider>
<DataTable
columns={columns}
queryKey="invoices"
queryFn={({ request, params }) =>
request
.get("/invoices", { params })
.then((response) => response.data)
}
request={apiClient}
responseKey="items"
create={false}
refresh
expandable={{
queryKey: "invoice-lines",
key: "lines",
parentParamKey: "eInvoiceEntryId",
nestedColumns,
queryFn: ({ request, params }) =>
request
.get("/lines", { params })
.then((response) => response.data),
}}
/>
</MantineProvider>
</QueryClientProvider>
);
}
export default App;Development
Clone the repository:
git clone <internal-repository-url>Navigate to the project:
cd tronstride-datatableInstall dependencies:
pnpm installStart Development Playground
Run:
pnpm devThis starts the Vite development environment and opens the DataTable demo/playground.
The playground can be used to:
- Test new DataTable features
- Test API integrations
- Test filtering
- Test sorting
- Test pagination
- Test expandable rows
- Verify UI changes
- Validate component behavior before releasing a new version
Build
Build the package using:
pnpm buildThe compiled package will be generated in:
dist/Before creating a release, verify that the generated dist directory contains the expected package files.
Project Structure
A recommended project structure is:
tronstride-datatable/
│
├── src/
│ ├── components/
│ │ └── DataTable/
│ ├── hooks/
│ ├── utils/
│ └── index.js
│
├── demo/
│ ├── src/
│ └── index.html
│
├── dist/
│
├── package.json
├── vite.config.js
├── README.md
└── pnpm-lock.yamlThe src directory contains the reusable package code.
The demo directory contains the internal development/playground application.
The dist directory contains the production build output.
Internal Package Guidelines
This package is shared across TronStride applications.
When modifying the DataTable:
- Avoid application-specific logic inside the shared component.
- Prefer configurable props over hard-coded business rules.
- Maintain backward compatibility whenever possible.
- Do not introduce dependencies unless they are required.
- Reuse existing project dependencies where possible.
- Test pagination, filtering, sorting, selection, and expandable rows after major changes.
- Test the component in the demo application before creating a release.
- Update this README when adding or changing public component APIs.
Adding New Features
Before adding a feature, determine whether it is:
Generic functionality
If the functionality can be used by multiple TronStride applications, it is a good candidate for the shared DataTable.
Examples:
- Column filtering
- Sorting
- Pagination
- Row selection
- Expandable rows
- Refresh
- Generic actions
- Loading states
- Empty states
Application-specific functionality
If functionality belongs to only one application or business domain, it should generally remain in the consuming application.
Examples:
- Invoice-specific approval rules
- Application-specific permissions
- Company-specific business calculations
- Domain-specific API transformations
Keep business logic outside the shared DataTable whenever possible.
Versioning
The package follows Semantic Versioning:
MAJOR.MINOR.PATCHExample:
1.0.0
1.1.0
1.1.1MAJOR
Breaking changes to the public API.
Example:
1.x.x → 2.x.xMINOR
New backward-compatible functionality.
Example:
1.1.0 → 1.2.0PATCH
Bug fixes and backward-compatible improvements.
Example:
1.1.0 → 1.1.1Release Process
Before releasing a new version:
1. Update the version
pnpm version patchor:
pnpm version minoror:
pnpm version major2. Install dependencies
pnpm install3. Run the build
pnpm build4. Test the demo
pnpm devVerify the main DataTable functionality.
5. Commit changes
git add .
git commit -m "release: update datatable"6. Push the changes
git push7. Publish
If the package is distributed through the company's internal npm registry:
pnpm publishFollow the company's internal package publishing and access-control process.
Consuming the Package
After a new version is released, update the package in a TronStride application:
pnpm update @tronstride/datatableOr install a specific version:
pnpm add @tronstride/[email protected]Troubleshooting
Package Not Found
If pnpm cannot find the package:
ERR_PNPM_FETCH_404verify that:
- The package exists in the internal registry.
- Your npm registry is configured correctly.
- You are authenticated.
- You have permission to access the package.
Check the configured registry:
npm config get registryBuild Issues
Run:
rm -rf node_modules
pnpm install
pnpm buildOn Windows PowerShell:
Remove-Item -Recurse -Force node_modules
pnpm install
pnpm buildReact Version Issues
Make sure the consuming application and DataTable package use compatible React versions.
Check:
pnpm list react react-domMantine Issues
Make sure Mantine styles are imported:
import "@mantine/core/styles.css";and that the application is wrapped with:
<MantineProvider>
<App />
</MantineProvider>React Query Issues
Make sure the DataTable is rendered inside:
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>Recommended Application Setup
A TronStride application using the DataTable should generally have a structure similar to:
Application
│
├── QueryClientProvider
│ │
│ ├── IntlProvider
│ │ │
│ │ └── MantineProvider
│ │ │
│ │ └── DataTable
│ │
│ └── Other Application Components
│
└── API / Request ClientThe DataTable should use the application's existing API client instead of creating a separate HTTP client.
Design Principles
@tronstride/datatable follows these principles:
Reusable
The component should work across different TronStride applications and business domains.
Server-Driven
Large datasets should be handled by the backend rather than loading the complete dataset into the browser.
Configurable
Application-specific behavior should be controlled through props and configuration.
Consistent
Common table functionality should behave consistently across TronStride applications.
Lightweight
Avoid unnecessary dependencies and application-specific logic inside the shared package.
Backward Compatible
Changes to the shared component should avoid breaking existing applications whenever possible.
Support
For issues, feature requests, or changes to the shared DataTable:
- Check the existing implementation and README.
- Reproduce the issue in the demo playground.
- Verify whether the issue is package-specific or application-specific.
- Create an issue or communicate with the TronStride frontend team.
- Include the package version and reproduction steps.
Internal Use Notice
@tronstride/datatable is an internal TronStride frontend component.
The package is intended to be consumed by authorized TronStride applications and team members.
Do not distribute the package outside the company's approved repositories, registries, or development environments without authorization.
License
Internal Use Only — TronStride
Copyright © TronStride.
This package is proprietary/internal software and is intended solely for authorized TronStride use.
