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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@niveshmintra/react-datatable

v0.1.2

Published

Headless, dependency-free React data table. Bring your own data source and styles.

Readme

@niveshmintra/react-datatable

Headless, dependency-free React data table. Bring your own data source and styles.

npm version CI license

Server-side or client-side sorting, pagination, search, and row selection — with zero runtime dependencies. The core (useDataTable) renders nothing; you own the markup. A styled <DataTable> component and an opt-in theme ship on top.

  • HeadlessuseDataTable returns a model; render whatever you want.
  • Or batteries-included<DataTable> ships accessible semantic markup.
  • Client or server mode — in-memory data, or an async data source.
  • Fully typed — generic over your row type; ships .d.ts (ESM + CJS).

Install

npm install @niveshmintra/react-datatable

react and react-dom (>=18) are peer dependencies.

Quick start (client mode)

import { useDataTable, type ColumnDef } from '@niveshmintra/react-datatable';

interface Person { id: number; name: string; age: number; }

const columns: ColumnDef<Person>[] = [
  { id: 'name', header: 'Name', accessorKey: 'name' },
  { id: 'age', header: 'Age', accessorKey: 'age' },
];

function People({ data }: { data: Person[] }) {
  const table = useDataTable({ columns, data, pageSize: 10 });

  return (
    <table>
      <thead>
        <tr>
          {table.headers.map((h) => (
            <th
              key={h.column.id}
              onClick={() => h.toggleSort()}
              aria-sort={
                h.sortDirection === 'asc'
                  ? 'ascending'
                  : h.sortDirection === 'desc'
                    ? 'descending'
                    : 'none'
              }
            >
              {String(h.column.header)}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {table.rows.map((row) => (
          <tr key={row.id}>
            {row.cells.map((cell) => (
              <td key={cell.column.id}>{cell.render()}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Or skip the markup entirely and use the styled component.

Columns

A ColumnDef<TData> describes one column. Resolve the value with either accessorKey (a flat key on the row) or accessorFn (derive it), and optionally override rendering with cell.

const columns: ColumnDef<Person>[] = [
  // Flat key.
  { id: 'name', header: 'Name', accessorKey: 'name' },

  // Derived value.
  { id: 'full', header: 'Full name', accessorFn: (p) => `${p.first} ${p.last}` },

  // Custom cell renderer (gets row, value, column, rowIndex).
  {
    id: 'age',
    header: 'Age',
    accessorKey: 'age',
    cell: ({ value }) => <strong>{value as number}</strong>,
  },

  // Action column: no accessor, sorting off, fixed width, pinned right.
  {
    id: 'actions',
    header: '',
    enableSorting: false,
    width: 80,
    pinned: 'right',
    cell: ({ row }) => <button onClick={() => edit(row)}>Edit</button>,
  },
];

| Field | Type | Notes | |-------|------|-------| | id | string | Required. Stable unique id (sort/selection key). | | header | ReactNode \| () => ReactNode | Header content. | | accessorKey | keyof TData & string | Read value from this key. | | accessorFn | (row) => unknown | Derive value (overrides accessorKey). | | cell | (ctx) => ReactNode | Custom renderer; falls back to raw value. | | enableSorting | boolean | Per-column sort toggle (default: table-level). | | width | number \| string | Fixed width; omit to flex-fill. | | pinned | 'left' \| 'right' | Sticky column side. | | meta | Record<string, unknown> | Untyped escape hatch for adapter data. |

Server mode

Pass dataSource instead of data. It's a single async function (state) => { rows, total } called whenever sort / page / search changes. Implement it with fetch, axios, Inertia, GraphQL — anything.

import { useDataTable, createRestDataSource } from '@niveshmintra/react-datatable';

const dataSource = createRestDataSource<Person>({
  url: '/api/people',
  method: 'POST',
});

function People() {
  const table = useDataTable({ columns, dataSource, pageSize: 20 });
  // table.isLoading, table.error, table.total ...
}

⚠️ Memoize the data source. A dataSource created inline in render is a new reference every render → infinite refetch loop. Define it at module scope (as above) or wrap it in useMemo:

const dataSource = useMemo(() => createRestDataSource<Person>({ url }), [url]);

createRestDataSource

A native-fetch helper (no axios). Both the request and response shapes are overridable to fit any backend.

Default request (method defaults to POST, sent as JSON body; GET sends query params):

{ "page": 1, "per_page": 20, "sort": [{ "id": "name", "dir": "asc" }], "search": "ann" }

Default response parsing reads rows from data or rows, and total from pagination.total or total (falling back to rows.length):

{ "data": [/* rows */], "pagination": { "total": 137 } }

Override either side:

createRestDataSource<Person>({
  url: '/api/people',
  headers: { Authorization: `Bearer ${token}` },
  serializeQuery: (s) => ({ offset: s.pagination.pageIndex * s.pagination.pageSize, q: s.search }),
  parseResponse: (json) => ({ rows: json.items, total: json.count }),
});

For auth/interceptors beyond headers, write your own DataSource function — the package knows nothing about your backend.

Styled component

import { DataTable } from '@niveshmintra/react-datatable';
import '@niveshmintra/react-datatable/styles.css'; // opt-in theme

<DataTable columns={columns} data={data} enableRowSelection onRowClick={open} />

Renders semantic <table> markup with stable rdt-* class names. Accessibility is built in: sortable headers are <button>s with aria-sort; row selection adds a select-all-on-page header checkbox. The theme is opt-in — every slot takes a class via classNames, and the component never depends on the CSS.

Sorting

Single-column by default — header click cycles asc → desc → cleared. Set enableMultiSort to stack columns with shift-click.

API reference

useDataTable(options) — options

| Option | Type | Default | Notes | |--------|------|---------|-------| | columns | ColumnDef<TData>[] | — | Required. | | data | TData[] | — | Client mode. Mutually exclusive with dataSource. | | dataSource | (state) => Promise<{ rows, total }> | — | Server mode. Memoize it. | | pageSize | number | 10 | Initial page size. | | enableSorting | boolean | true | Global toggle; per-column overrides. | | enableMultiSort | boolean | false | Shift-click to stack sorts. | | enableRowSelection | boolean | false | | | getRowId | (row, index) => string | array index | Stable row id. | | searchDebounceMs | number | 300 | Debounce before search recomputes/refetches. |

Return value — DataTableInstance

| Member | Type | Notes | |--------|------|-------| | headers | HeaderCell[] | column, sortDirection, canSort, toggleSort(additive?). | | rows | TableRow[] | id, original, index, selected, toggleSelected(), cells[]. | | pagination | { pageIndex, pageSize } | | | pageCount / total | number | | | setPageIndex / setPageSize | (n) => void | | | nextPage / previousPage | () => void | | | canNextPage / canPreviousPage | boolean | | | search / setSearch | string / (v) => void | Debounced. | | sorting | ColumnSort[] | Active sort state. | | selectedRowIds | string[] | | | clearSelection | () => void | | | isLoading / error | boolean / Error \| null | Server mode. | | refresh | () => void | Force refetch (server) / recompute (client). |

<DataTable> — extra props

Accepts every useDataTable option, plus:

| Prop | Type | Default | Notes | |------|------|---------|-------| | className | string | — | Applied to the root. | | classNames | per-slot object | — | Override root, toolbar, search, tableWrapper, table, th, td, tr, pagination. | | showToolbar | boolean | true | Search toolbar. | | showPagination | boolean | true | Pagination footer. | | emptyMessage | ReactNode | 'No rows' | | | loadingMessage | ReactNode | 'Loading…' | Shown during server fetch. | | searchPlaceholder | string | 'Search…' | | | pageSizeOptions | number[] | [10, 20, 50, 100] | | | onRowClick | (row: TData) => void | — | Adds a pointer cursor to rows. |

Changelog

See CHANGELOG.md.

License

MIT