nc-table-react
v0.4.0
Published
Advanced React table component with search functionality, backend filter integration, and customizable response formats
Maintainers
Readme
nc-table-react
Flexible React table component with advanced search, actions, settings, selection, and pagination.
A production-ready, feature-rich table component that supports both static and server-side data with powerful filtering capabilities.
🚀 Features
- ✅ Static & Server-side Data - Works with local arrays or async data handlers
- ✅ Advanced Search - 5 data types with 12 comparison operators
- ✅ Structured Filter Format - Backend-friendly
${column}${operator}${value};And$...format - ✅ Backend Field Mapping - Map display column names to different backend field names
- ✅ Row Selection - Single and multi-row selection with bulk actions
- ✅ Sorting & Pagination - Built-in sorting and configurable pagination
- ✅ CRUD Operations - Built-in delete functionality with confirmation dialogs
- ✅ Export Features - CSV, Excel, PDF export options
- ✅ Serial Number Column - Optional row numbering with pagination-aware calculation
- ✅ Conditional Actions - Show/hide table actions based on item data or static conditions
- ✅ Responsive Design - Mobile-optimized interface
- ✅ TypeScript - Full type safety and IntelliSense support
- ✅ Accessibility - ARIA labels and keyboard navigation
- ✅ Internationalization - i18n ready with react-i18next
📦 Installation
npm install nc-table-react
# Install peer dependencies:
npm install react-i18next i18next lucide-react clsx class-variance-authority tailwind-merge date-fns
# Install Radix UI dependencies:
npm install @radix-ui/react-alert-dialog @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-toastRequirements:
- React 18+
- TypeScript (recommended)
🔧 Troubleshooting Installation
If you encounter a createSlot import error, install the correct radix-ui version:
npm install @radix-ui/react-slot@^1.0.2See full installation troubleshooting guide
🎯 Quick Start
import React from "react";
import { NcTable, type Column, type TableAction } from "nc-table-react";
import i18n from "i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
// Minimal i18n setup
if (!i18n.isInitialized) {
i18n
.use(initReactI18next)
.init({ lng: "en", resources: { en: { translation: {} } } });
}
type User = {
id: number;
name: string;
email: string;
status: "active" | "inactive";
};
const data: User[] = [
{ id: 1, name: "Ada Lovelace", email: "[email protected]", status: "active" },
{ id: 2, name: "Alan Turing", email: "[email protected]", status: "inactive" },
];
const columns: Column<User>[] = [
{ key: "name", header: "Name" },
{ key: "email", header: "Email" },
{ key: "status", header: "Status" },
];
const actions: TableAction<User>[] = [
{
label: "View",
onClick: (u) => alert(`Viewing ${u.name}`),
show: true, // Always show
},
{
label: "Activate",
onClick: (u) => alert(`Activating ${u.name}`),
show: (u) => u.status === "inactive", // Only show for inactive users
},
];
export default function App() {
return (
<I18nextProvider i18n={i18n}>
<NcTable<User>
id="my-table"
data={data}
columns={columns}
actions={actions}
idField="id"
showSerialNumber={true}
/>
</I18nextProvider>
);
}🔢 Serial Number Column
The table includes an optional serial number column that shows row numbers based on the current page and position.
Basic Usage
<NcTable
data={data}
columns={columns}
showSerialNumber={true} // Default: true
idField="id"
/>Hide Serial Numbers
<NcTable
data={data}
columns={columns}
showSerialNumber={false} // Hide serial number column
idField="id"
/>Serial Number Calculation
Serial numbers are calculated to be continuous across pages:
- Page 1 (pageSize=5): 1, 2, 3, 4, 5
- Page 2 (pageSize=5): 6, 7, 8, 9, 10
- Page 3 (pageSize=5): 11, 12, 13, 14, 15
Formula: (currentPage - 1) × pageSize + index + 1
🆔 Unique Table Instance IDs
Each table instance can have a unique identifier for better isolation, testing, and debugging when using multiple tables.
Basic Usage
<NcTable
id="users-table" // Unique ID for this table instance
data={data}
columns={columns}
idField="id"
/>Benefits of Using IDs
- DOM Targeting: Direct access via
document.getElementById('users-table') - Testing: Easy targeting with
getByTestId('users-table') - Analytics: Track specific table interactions
- Accessibility: Screen readers can identify specific tables
- CSS Targeting: More specific styling with
#users-table .table-header
🎯 Conditional Table Actions
Table actions can be shown or hidden based on item data or static conditions using the show property.
Static Visibility
const actions: TableAction<User>[] = [
{
label: "Edit",
icon: "Edit",
onClick: (user) => console.log("Edit", user),
show: true, // Always show
},
{
label: "Archive",
icon: "Archive",
onClick: (user) => console.log("Archive", user),
show: false, // Never show
},
];Dynamic Visibility Based on Item Data
const actions: TableAction<User>[] = [
{
label: "Activate",
icon: "User",
onClick: (user) => console.log("Activate", user),
show: (user) => user.status === "inactive", // Only show for inactive users
},
{
label: "Deactivate",
icon: "User",
onClick: (user) => console.log("Deactivate", user),
show: (user) => user.status === "active", // Only show for active users
},
{
label: "Delete",
icon: "Trash",
onClick: (user) => console.log("Delete", user),
variant: "destructive",
show: (user) => user.role !== "Admin", // Hide delete for admins
},
{
label: "Admin Actions",
icon: "Settings",
onClick: (user) => console.log("Admin Actions", user),
show: (user) => user.role === "Admin", // Only show for admins
},
];Show Property Options
| Value | Description |
| ---------------------- | ----------------------------------------- |
| undefined | Default behavior - always show the action |
| true | Always show the action |
| false | Never show the action |
| (item: T) => boolean | Show based on item data evaluation |
Smart Action Column Display
The actions column (dropdown menu) will only appear for rows that have at least one visible action. If all actions for a row are hidden, the actions column won't be rendered for that row.
📊 Usage Modes: Client-side vs Server-side
nc-table-react automatically detects the usage mode based on the props you provide. No manual configuration needed!
🖥️ Client-side Mode (Static Data)
Triggered when: You provide the data prop
<NcTable
data={users} // ✅ Static array provided
columns={columns}
actions={actions}
idField="id"
// No handler needed
/>How it works:
- ✅ Table uses your static
dataarray directly - ✅ Filtering/sorting happens in memory on the frontend
- ✅ No API calls are made
- ✅ Perfect for small datasets or pre-loaded data
- ✅ Instant search and sorting
🌐 Server-side Mode (Dynamic Data)
Triggered when: You provide the handler prop
<NcTable
columns={columns}
handler={fetchTableData} // ✅ Async function provided
actions={actions}
idField="id"
// No data prop needed
/>How it works:
- ✅ Table calls your
handlerfunction for data - ✅ Filtering/sorting happens on your backend
- ✅ API calls made for pagination, search, sorting
- ✅ Perfect for large datasets or real-time data
- ✅ Advanced search filters sent to your backend
📋 Mode Comparison
| Feature | Client-side (data prop) | Server-side (handler prop) |
| ---------------------- | ------------------------- | ---------------------------- |
| Data Source | Static array | API function |
| Filtering | Frontend (JavaScript) | Backend (your API) |
| Sorting | Frontend (JavaScript) | Backend (your API) |
| Pagination | Frontend pagination | Server-side pagination |
| API Calls | ❌ None | ✅ Yes (automatic) |
| Best For | Small datasets, demos | Large datasets, production |
| Search Performance | Instant | Depends on backend |
🎯 Important Notes
- Mutually Exclusive: Provide either
dataORhandler, not both - Automatic Detection: The table knows what to do based on your props
- Same API: Identical component interface for both modes
- Filter Format: In server-side mode, advanced search filters are sent as structured strings to your backend
🌐 Server-side Data (Detailed Example)
import type { NcTableProps } from "nc-table-react";
type Item = { id: number; name: string };
const handler: NcTableProps<Item>["handler"] = async (params) => {
// params: { PageNumber, PageSize, Filter?, Order? }
console.log("Received params:", params);
// Example server request
const response = await fetch("/api/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
});
return response.json(); // Returns IApiResponse<Item[]>
};
// Usage
<NcTable<Item> columns={[{ key: "name", header: "Name" }]} handler={handler} />;Backend Filter Format
The component generates structured filter strings for your backend:
// Single condition
"name~=John;"
// Multiple conditions
"name~=John;And$department==Engineering;And$salary>50000;"
// Backend receives:
{
"PageNumber": 1,
"PageSize": 10,
"Filter": "name~=John;And$department==Engineering;And$salary>50000;",
"Order": "name;Asc"
}🔍 Advanced Search & Data Types
Supported Data Types
1. Text (Default)
{
key: "name",
header: "Name",
searchable: true,
// No searchOverride needed - text is default
}2. Date
{
key: "startDate",
header: "Start Date",
searchable: true,
searchOverride: {
dataType: "date"
},
render: (item) => new Date(item.startDate).toLocaleDateString(),
}3. Select (Dropdown)
{
key: "department",
header: "Department",
searchable: true,
searchOverride: {
dataType: "select",
selectOptions: [
{ text: "Engineering", value: "Engineering" },
{ text: "Marketing", value: "Marketing" },
{ text: "Sales", value: "Sales" },
]
}
}4. Boolean (Yes/No with true/false)
{
key: "isActive",
header: "Is Active",
searchable: true,
searchOverride: {
dataType: "boolean"
},
render: (item) => item.isActive ? "Yes" : "No",
}5. YesOrNo (Yes/No with 1/0)
{
key: "hasAccess",
header: "Has Access",
searchable: true,
searchOverride: {
dataType: "YesOrNo"
},
render: (item) => item.hasAccess === 1 ? "Yes" : "No",
}Search Operators
The component provides 12 powerful comparison operators:
- Equals (
==) - Exact match - Not Equals (
!=) - Does not match - Greater Than (
>) - Numeric/date comparison - Greater Than or Equals (
>=) - Numeric/date comparison - Less Than (
<) - Numeric/date comparison - Less Than or Equals (
<=) - Numeric/date comparison - Contains (
~=) - Substring match - Not Contains (
!~=) - Does not contain substring - Starts With (
_=) - Begins with pattern - Not Starts With (
!_=) - Does not begin with pattern - Ends With (
|=) - Ends with pattern - Not Ends With (
!|=) - Does not end with pattern
🔑 Backend Field Mapping (searchOverride.key)
When your display column names differ from backend field names, use the key property in searchOverride:
const columns: Column<Employee>[] = [
{
key: "statusDescription", // Display field: "Active Employee"
header: "Status Description",
searchable: true,
searchOverride: {
key: "Status", // 🎯 Backend field: sends "Status==1;"
dataType: "select",
selectOptions: [
{ text: "Active Employee", value: "1" },
{ text: "Inactive Employee", value: "0" },
{ text: "Pending Approval", value: "2" },
],
},
},
{
key: "departmentName", // Display field: "Engineering Dept"
header: "Department Name",
searchable: true,
searchOverride: {
key: "DeptId", // 🎯 Backend field: sends "DeptId==ENG;"
dataType: "select",
selectOptions: [
{ text: "Engineering Dept", value: "ENG" },
{ text: "Marketing Dept", value: "MKT" },
],
},
},
{
key: "name", // Same for display and backend
header: "Name",
searchable: true,
// No key override - uses "name" for both display and backend
},
];Generated Filter Examples
// User searches: "Status Description" = "Active Employee"
// Generated: "Status==1;"
// User searches: "Department Name" = "Engineering Dept" AND "Status Description" = "Active Employee"
// Generated: "DeptId==ENG;And$Status==1;"
// User searches: "Name" contains "John"
// Generated: "name~=John;"Use Cases
- Database Fields: Display
firstNamebut search onfirst_name - Enum Values: Display "Active Employee" but search on numeric status codes
- Normalized IDs: Display department names but search on department IDs
- Legacy APIs: Map modern UI field names to legacy backend fields
🎛️ Complete Feature Example
import React, { useState } from "react";
import { NcTable, type Column, type TableAction } from "nc-table-react";
type Employee = {
id: number;
name: string;
email: string;
department: string;
startDate: string;
isActive: boolean;
hasAccess: number;
salary: number;
status: "active" | "inactive" | "pending";
};
const columns: Column<Employee>[] = [
{
key: "name",
header: "Name",
searchable: true,
},
{
key: "department",
header: "Department",
searchable: true,
searchOverride: {
dataType: "select",
selectOptions: [
{ text: "Engineering", value: "Engineering" },
{ text: "Marketing", value: "Marketing" },
{ text: "Sales", value: "Sales" },
],
},
},
{
key: "startDate",
header: "Start Date",
searchable: true,
searchOverride: { dataType: "date" },
render: (emp) => new Date(emp.startDate).toLocaleDateString(),
},
{
key: "isActive",
header: "Is Active",
searchable: true,
searchOverride: { dataType: "boolean" },
render: (emp) => (emp.isActive ? "Active" : "Inactive"),
},
{
key: "salary",
header: "Salary",
searchable: true,
render: (emp) => `$${emp.salary.toLocaleString()}`,
},
];
const actions: TableAction<Employee>[] = [
{
label: "Edit",
onClick: (emp) => console.log("Edit", emp),
icon: "edit",
show: true, // Always show
},
{
label: "Activate",
onClick: (emp) => console.log("Activate", emp),
icon: "user",
show: (emp) => emp.status === "inactive", // Only show for inactive employees
},
{
label: "Deactivate",
onClick: (emp) => console.log("Deactivate", emp),
icon: "user",
show: (emp) => emp.status === "active", // Only show for active employees
},
{
label: "Delete",
onClick: (emp) => console.log("Delete", emp),
variant: "destructive",
icon: "trash",
show: (emp) => emp.role !== "Admin", // Hide delete for admins
},
];
export default function AdvancedExample() {
const [employees, setEmployees] = useState<Employee[]>([
// ... your data
]);
const handleAdvancedSearch = (filters) => {
console.log("Search filters:", filters);
// Handle filtering logic
};
const handleBulkAction = (
action: string,
selectedIds: (string | number)[]
) => {
console.log(`Bulk ${action} on:`, selectedIds);
};
const handleExport = (format: string, selectedIds?: (string | number)[]) => {
console.log(`Export as ${format}:`, selectedIds || "all");
};
const handleDelete = async (ids: (string | number)[]) => {
// Delete implementation
console.log("Deleting:", ids);
return { Succeeded: true, Message: "Deleted successfully" };
};
return (
<NcTable<Employee>
// Data
data={employees}
columns={columns}
actions={actions}
idField="id"
// Serial Numbers
showSerialNumber={true}
// Search & Filtering
showAdvancedSearch={true}
onAdvancedSearch={handleAdvancedSearch}
enableInternalSearch={true}
// Selection & Bulk Actions
selectable={true}
bulkActions={[
{ value: "export", label: "Export Selected" },
{ value: "archive", label: "Archive Selected" },
{ value: "delete", label: "Delete Selected", variant: "destructive" },
]}
onBulkAction={handleBulkAction}
// Export
exportOptions={[
{ format: "csv", label: "Export as CSV" },
{ format: "excel", label: "Export as Excel" },
{ format: "pdf", label: "Export as PDF" },
]}
onExport={handleExport}
// CRUD Operations
canDelete={true}
removeItemHandler={handleDelete}
// Pagination
enableInternalPagination={true}
pageSize={10}
paginationProps={{
showFirstLast: true,
showEllipsis: true,
maxVisiblePages: 5,
}}
// Settings
enableInternalSettings={true}
defaultSettings={{
pageSize: 10,
sortBy: "name",
sortDirection: "Asc",
}}
// Customization
searchPlaceholder="Search employees..."
emptyStateMessage="No employees found"
className="my-custom-table"
/>
);
}📖 API Reference
Core Props
| Prop | Type | Description |
| ------------------- | ------------------ | ------------------------------------------- |
| data? | T[] | Static data array |
| columns | Column<T>[] | Table column definitions |
| handler? | DataHandler<T> | Async data function for server-side data |
| actions? | TableAction<T>[] | Row action menu items |
| idField? | keyof T | Unique identifier field (default: "Id") |
| showSerialNumber? | boolean | Show serial number column (default: true) |
| id? | string | Unique identifier for this table instance |
Search & Filtering
| Prop | Type | Description |
| ----------------------- | ----------------------------------- | --------------------------------------- |
| showAdvancedSearch? | boolean | Enable advanced search UI |
| onAdvancedSearch? | (filters: SearchFilter[]) => void | Advanced search callback |
| enableInternalSearch? | boolean | Enable internal search state management |
| searchPlaceholder? | string | Search input placeholder |
Selection & Bulk Actions
| Prop | Type | Description |
| -------------------- | ----------------------------------------------------- | -------------------------------------- |
| selectable? | boolean | Enable row selection (default: true) |
| selectedIds? | (string \| number)[] | Controlled selected IDs |
| onSelectionChange? | (ids: (string \| number)[]) => void | Selection change callback |
| bulkActions? | BulkAction[] | Bulk action definitions |
| onBulkAction? | (action: string, ids: (string \| number)[]) => void | Bulk action callback |
Export Features
| Prop | Type | Description |
| ---------------- | ------------------------------------------------------ | --------------------- |
| exportOptions? | ExportOption[] | Export format options |
| onExport? | (format: string, ids?: (string \| number)[]) => void | Export callback |
CRUD Operations
| Prop | Type | Description |
| -------------------- | --------------------------------------------------------------- | --------------------------- |
| canDelete? | boolean | Enable delete functionality |
| removeItemHandler? | (ids: (string \| number)[]) => Promise<IApiResponse<unknown>> | Delete handler |
Pagination
| Prop | Type | Description |
| --------------------------- | ----------------- | ------------------------------------------ |
| enableInternalPagination? | boolean | Enable internal pagination |
| showPagination? | boolean | Show pagination controls |
| pageSize? | number | Items per page |
| currentPage? | number | Current page (controlled) |
| totalItems? | number | Total item count (for external pagination) |
| paginationProps? | PaginationProps | Pagination customization |
Settings & Customization
| Prop | Type | Description |
| ------------------------- | ----------------------------------- | -------------------------- |
| enableInternalSettings? | boolean | Enable settings management |
| settings? | TableSettings | Controlled settings |
| onSettingsChange? | (settings: TableSettings) => void | Settings change callback |
| defaultSettings? | Partial<TableSettings> | Default settings |
| className? | string | Custom CSS class |
| loading? | boolean | External loading state |
Core Types
type Column<T> = {
key: string;
header: string;
render?: (item: T) => React.ReactNode;
width?: string;
visible?: boolean;
searchable?: boolean;
searchOverride?: NcTableSearchOverride;
};
type TableAction<T> = {
label: string;
icon?: React.ReactNode | string;
onClick: (item: T) => void;
variant?: "default" | "destructive";
className?: string;
separator?: boolean;
show?: boolean | ((item: T) => boolean);
};
type DataHandler<T> = (params: {
PageNumber: number;
PageSize: number;
Filter?: string;
Order?: string;
}) => Promise<IApiResponse<T[]>>;
interface IApiResponse<T> {
Succeeded: boolean;
Data: T;
Count: number;
Message?: string;
}
type NcTableSearchDataType = "text" | "date" | "select" | "boolean" | "YesOrNo";
interface NcTableSearchOverride {
key?: string; // Backend field name to use instead of column name
dataType: NcTableSearchDataType;
selectOptions?: Array<{ text: string; value: unknown }>;
}🎨 Styling
The component uses Tailwind CSS utility classes and is designed to work seamlessly in Tailwind projects. It also works without Tailwind, using sensible defaults.
Custom Styling
<NcTable
className="my-custom-table"
// ... other props
/>Responsive Design
The table is fully responsive and adapts to different screen sizes:
- Mobile: Stacked layout with horizontal scrolling
- Tablet: Optimized column widths
- Desktop: Full feature display
🌍 Internationalization
The component is i18n ready and uses react-i18next for translations:
// Add translations to your i18n resources
const resources = {
en: {
translation: {
"Search...": "Search...",
"No items found": "No items found",
// ... other translations
},
},
};🧪 Testing
The package includes comprehensive test coverage. To run tests:
npm test📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
📞 Support
- 📚 Documentation: This README
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
🔗 Repository
- GitHub: NextCounsel/NCTable
- npm: nc-table-react
Built with ❤️ for modern React applications
📝 Changelog
v0.3.6 (Latest)
✨ New Features
- Unique Table Instance IDs: Added
idprop for unique table identification- Enables better isolation when using multiple table instances
- Supports DOM targeting, testing, analytics, and accessibility
- Optional prop with no breaking changes
📚 Documentation
- Updated README with ID prop usage and benefits
- Enhanced Multiple Instances Guide with ID best practices
- Added comprehensive examples and testing strategies
v0.3.5
✨ New Features
- Serial Number Column: Added
showSerialNumberprop to control display of row numbers- Defaults to
true - Shows pagination-aware serial numbers:
(currentPage - 1) × pageSize + index + 1 - Header displays "S/No"
- Defaults to
- Conditional Table Actions: Added
showproperty toTableActioninterface- Static visibility:
show: true | false - Dynamic visibility:
show: (item) => boolean - Smart action column display - only shows when there are visible actions
- Static visibility:
🔧 Improvements
- Enhanced action rendering logic with conditional visibility
- Improved table layout with optional serial number column
- Better TypeScript support for new properties
📚 Documentation
- Added comprehensive examples for new features
- Updated API reference with new props
- Enhanced usage examples with conditional actions
v0.3.4
- Fixed React JSX runtime bundling issues
- Improved React 18.3.1 compatibility
- Resolved
ReactCurrentDispatchererrors
v0.3.3
- Initial stable release
- Core table functionality
- Advanced search and filtering
- Server-side data support
