npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

nc-table-react

v0.4.0

Published

Advanced React table component with search functionality, backend filter integration, and customizable response formats

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-toast

Requirements:

  • 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.2

See 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 data array 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 handler function 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 data OR handler, 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 firstName but search on first_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

Built with ❤️ for modern React applications


📝 Changelog

v0.3.6 (Latest)

✨ New Features

  • Unique Table Instance IDs: Added id prop 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 showSerialNumber prop to control display of row numbers
    • Defaults to true
    • Shows pagination-aware serial numbers: (currentPage - 1) × pageSize + index + 1
    • Header displays "S/No"
  • Conditional Table Actions: Added show property to TableAction interface
    • Static visibility: show: true | false
    • Dynamic visibility: show: (item) => boolean
    • Smart action column display - only shows when there are visible actions

🔧 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 ReactCurrentDispatcher errors

v0.3.3

  • Initial stable release
  • Core table functionality
  • Advanced search and filtering
  • Server-side data support