Skip to content

ascentspark/react-datatables

Repository files navigation

@ascentsparksoftware/react-datatables-net

A React wrapper for DataTables.net, by Ascentspark

Ascentspark

Typed React components for DataTables.net — no jQuery in your code

npm version downloads React 19 license MIT

🚀 Live demo  ·  ✏️ Edit in place  ·  📦 npm  ·  📝 Changelog


DataTables turns a plain HTML table into an interactive one: sorting, search, pagination, and a large set of extensions. @ascentsparksoftware/react-datatables-net lets you drive it from React without writing any jQuery. Render a <DataTable> component, pass your options and data as props, and read events back as typed callbacks. Client-only by design, StrictMode-safe, React Compiler-safe, Next.js friendly, and escaped by default. It is the React sibling of our Angular wrapper, ngx-datatables-net, and shares its design contracts.

⚛️ Built the modern React way. Function components, portals for live React cell content, and DataTables' non-jQuery constructor API. Your only peer dependencies are React and DataTables: no jquery required of your app. The engine is loaded lazily inside an effect, so jQuery and DataTables stay out of your initial bundle and out of the Next.js server graph. (DataTables uses jQuery internally, so it still ships as a transitive dependency, but you never write or configure it.)

🆓 Built-in inline editing, an alternative to the paid version

DataTables' own inline editing ships only in Editor, a commercial paid extension. This package gives you double-click edit-in-place for free (MIT): text, textarea, number, date, checkbox, select, multiselect and custom React component editors, with validation, keyboard navigation and optional pessimistic async saving, all through the non-jQuery API and written the React way.

Try the live demo  ·  Read the guide

✨ Why @ascentsparksoftware/react-datatables-net

  • No jQuery in your code. Built on the DataTables non-jQuery API; jQuery stays a transitive dependency you never touch.
  • Client-only with lazy engine loading. The DataTables constructor is dynamically imported inside an effect, so importing the module on the server (Next.js does, even for never-rendered components) touches no DOM, and jQuery + the engine stay out of the initial bundle and the server graph.
  • Props in, callbacks out. Pass a new data array and the table reconciles itself via the cheap clear → rows.add → draw path — no manual redraw trigger to remember. Events come back as typed callback props; the underlying Api is one ref away.
  • Live React cell content. A column's cell render prop renders real React — hooks, context and event handlers all work — while sorting and search keep operating on the raw data.
  • Free edit-in-place. Inline editing without the commercial Editor licence (see the callout above).
  • StrictMode-safe and React Compiler-safe. Init is idempotent, cleanup is complete, and nothing mutated is read during render — the dev double-mount never leaks an instance or a listener.
  • Safe by default. escapeByDefault escapes every column without an explicit renderer; unsafeHtmlRenderer() is the explicit, named escape hatch for trusted HTML.
  • Every extension works. Buttons, Select, Responsive, FixedHeader/Columns, Scroller, RowGroup, SearchPanes, ColumnControl and more: pass the config straight through.
  • Four styling adapters. DataTables default, Bootstrap 5, plus Tailwind and Material themes authored by us (DataTables ships neither).

📦 Install

npm install @ascentsparksoftware/react-datatables-net datatables.net datatables.net-dt

Add the stylesheet for your chosen theme once, e.g. in app/layout.tsx (Next.js) or main.tsx (Vite):

import 'datatables.net-dt/css/dataTables.dataTables.css';

🚀 Quick start

Configure the provider once, near the root of your app:

// providers.tsx
import { DataTablesProvider } from '@ascentsparksoftware/react-datatables-net';
import { defaultStyling } from '@ascentsparksoftware/react-datatables-net/dt';

<DataTablesProvider styling={defaultStyling} escapeByDefault options={{ pageLength: 10 }}>
  <App />
</DataTablesProvider>

Then render a table:

'use client';

import { useState } from 'react';
import { DataTable, type Api, type ConfigColumns } from '@ascentsparksoftware/react-datatables-net';

interface Employee {
  id: number;
  name: string;
  position: string;
  office: string;
}

const columns: ConfigColumns[] = [
  { data: 'id', title: 'ID' },
  { data: 'name', title: 'Name' },
  { data: 'position', title: 'Position' },
  { data: 'office', title: 'Office' },
];

export default function EmployeesPage() {
  const [data] = useState<Employee[]>([
    { id: 1, name: 'Ada Lovelace', position: 'Engineer', office: 'London' },
    { id: 2, name: 'Linus Torvalds', position: 'Maintainer', office: 'Portland' },
  ]);

  return (
    <DataTable<Employee>
      className="display"
      style={{ width: '100%' }}
      data={data}
      columns={columns}
      onInit={(api: Api<Employee>) => {
        // escape hatch: the full DataTables Api for imperative calls
      }}
      onRowClick={({ row }) => {
        // row is the typed Employee that was clicked
      }}
    >
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Position</th>
          <th>Office</th>
        </tr>
      </thead>
    </DataTable>
  );
}

To reload the table later, set a new array into state and pass it as data. That is the whole loop.

✏️ Edit in place (free, no Editor licence)

Give a column an editor. Double-click to edit; Enter commits, Escape cancels, Tab moves to the next editable cell.

import { DataTable, type DtColumn } from '@ascentsparksoftware/react-datatables-net';

const columns: DtColumn<User>[] = [
  { data: 'name',   title: 'Name',   editor: { type: 'text', validate: v => String(v).trim() ? null : 'Required' } },
  { data: 'role',   title: 'Role',   editor: { type: 'select', options: ROLES } },
  { data: 'skills', title: 'Skills', editor: { type: 'multiselect', options: SKILLS } },
];

<DataTable<User> data={users} columns={columns} save={saveHandler} />

Editors: text, textarea, number, date, checkbox, select, multiselect, custom (your own React component). Pass a save handler to persist before the cell is written (sync or Promise). The cell shows a busy state and, on failure, stays open with an inline error for retry. Full details in the Edit in place guide.

🧩 <DataTable> props

All props are optional. T is the row type (<DataTable<Employee> …>).

Prop Type Description
options Config DataTables options object. A structural change (new keys/values, not a new inline literal that serializes identically) recreates the table. Merged over the provider's default options.
data readonly T[] Row data. A new array reference replaces the rows without re-initializing (clear → rows.add → draw(false), keeping the paging position).
columns DtColumn<T>[] Column definitions (convenience for options.columns). Columns may carry a cell render prop and/or an editor (see the table below). Structural change recreates the table.
escapeByDefault boolean Override the provider's escapeByDefault for this table (see Security).
styling DtStyling Override the provider's styling adapter for this table.
className string Class applied to the <table> element (e.g. display for the default theme).
style CSSProperties Inline style for the <table> element.
id string id for the <table> element.
children ReactNode Table markup: <thead> (and optionally <tfoot> / a DOM-sourced <tbody>).
ref Ref<DataTableRef<T>> Imperative access to the DataTables Api (ref.current.api, null until initialized).
onInit (api: Api<T>) => void Called once with the Api immediately after the table is created.
onDraw (event: DtEvent<T>) => void DataTables draw event. event.page carries an api.page.info() snapshot.
onPage (event: DtEvent<T>) => void DataTables page event. event.page carries an api.page.info() snapshot.
onXhr (event: DtEvent<T>) => void DataTables xhr event (Ajax/server-side data load).
onSelect (event: DtSelectEvent<T>) => void Select extension select event.
onDeselect (event: DtSelectEvent<T>) => void Select extension deselect event.
onSelectionChange (selected: readonly T[]) => void Current selection (row data) after every select/deselect. Requires the Select extension.
onRowClick (event: DtRowClickEvent<T>) => void Row click (delegated listener on <tbody>), with resolved row data, index, <tr> element and the mouse event.
save DtCellSaveHandler<T> Optional pessimistic save handler for edit-in-place. Runs on commit before the cell is written; return a Promise to defer the write, reject/throw to keep the cell unchanged and leave the editor open for retry.
onCellEditStart (ctx: DtEditContext<T>) => void Emitted when an edit begins (editor opened).
onCellEdit (commit: DtCellEditCommit<T>) => void Emitted after a changed value is validated, saved and written to the cell.
onCellEditCancel (evt: DtCellEditCancel<T>) => void Emitted when an edit is abandoned (Escape, blur with no change, programmatic close, …).
onCellEditError (evt: DtCellEditError<T>) => void Emitted when a save handler fails; the cell is left unchanged.

Inline callback props are safe: the component reads the latest callbacks from a ref, so a new inline function never forces a re-initialization. The same applies to a column's cell render prop and editor callbacks — swapping the function (same column structure) takes effect on the next draw without recreating the table. The styling prop is compared by a structural signature too, so an inline withTailwind({...}) per render does not recreate the table.

Server-side tip: a data: null action column should either carry a cell render prop (the component's display shim supplies the cell content) or set DataTables' defaultContent — otherwise DataTables warns about the missing data source in serverSide mode.

DtColumn<T> extras

DtColumn<T> is DataTables' own ConfigColumns plus two fields:

Field Type Description
cell (ctx: DtCellContext<T>) => ReactNode React render prop for the cell's display content. Renders as live React (portals from the component's own tree, so hooks, context and event handlers work), re-rendered across draws. Sorting/filtering still use the raw data. Context: { cellData, row, rowIndex, colIndex }.
editor DtEditorConfig<T> Edit-in-place configuration; omit for a read-only column.

Typed Api helpers

Helper Signature Description
ajaxParams (api: Api<T>) => AjaxData | undefined Typed accessor for api.ajax.params() (upstream types it as a bare object): the last Ajax/server-side request parameters, or undefined when the table has no Ajax source.
pageInfo (api: Api<T>) => ApiPageInfo Typed convenience for api.page.info().

Editor kinds

editor.type Control Committed value
text <input type="text"> string
textarea <textarea> string (commit with Ctrl/⌘+Enter, or blur)
number <input type="number"> number, or null when blank
date <input type="date"> ISO yyyy-mm-dd string, or null when blank
checkbox checkbox boolean
select <select> the chosen option's value
multiselect <select multiple> an array of the chosen option values
custom your React component whatever your component passes to commit()

All editors share the optional ariaLabel, disabled: (ctx) => boolean and validate: (value, row) => string \| null \| undefined fields. See the Edit in place guide for everything.

🎨 Styling adapters

Pick one adapter and pass it to DataTablesProvider (or per table via the styling prop). Each adapter lazily imports its DataTables constructor, so nothing touches the DOM at module import time.

DataTables default theme@ascentsparksoftware/react-datatables-net/dt

import { defaultStyling } from '@ascentsparksoftware/react-datatables-net/dt';
// stylesheet (once, in your root layout / entry):
import 'datatables.net-dt/css/dataTables.dataTables.css';

<DataTablesProvider styling={defaultStyling}></DataTablesProvider>

Bootstrap 5@ascentsparksoftware/react-datatables-net/bs5 (needs the datatables.net-bs5 peer)

import { bootstrap5Styling } from '@ascentsparksoftware/react-datatables-net/bs5';
// stylesheets:
import 'bootstrap/dist/css/bootstrap.css';
import 'datatables.net-bs5/css/dataTables.bootstrap5.css';

// tables typically use className="table table-striped" instead of "display"
<DataTablesProvider styling={bootstrap5Styling}></DataTablesProvider>

Tailwind@ascentsparksoftware/react-datatables-net/tailwind, authored by us (DataTables ships no official Tailwind package). Self-contained, scoped stylesheet — Tailwind v4-compatible plain CSS, no Tailwind build step required, themeable via CSS custom properties:

import { tailwindStyling } from '@ascentsparksoftware/react-datatables-net/tailwind';
// stylesheet:
import '@ascentsparksoftware/react-datatables-net/tailwind/styles/react-datatables-net.tailwind.css';

<DataTablesProvider styling={tailwindStyling}></DataTablesProvider>

Material@ascentsparksoftware/react-datatables-net/material, authored by us (DataTables ships no official Material package). Follows Material Design conventions, themeable via CSS custom properties:

import { materialStyling } from '@ascentsparksoftware/react-datatables-net/material';
// stylesheet:
import '@ascentsparksoftware/react-datatables-net/material/styles/react-datatables-net.material.css';

<DataTablesProvider styling={materialStyling}></DataTablesProvider>

The Tailwind and Material adapters scope their stylesheets under a class (rdt-tailwind / rdt-material) that the component adds to the DataTables container automatically.

Overriding the scope class detaches the shipped stylesheet. The CSS is hard-scoped to the default class, so withTailwind({ scopeClass: 'my-scope' }) renders the table unstyled unless you also ship your own copy of the stylesheet rescoped under .my-scope. To theme the table, keep the default scope and override the CSS custom properties (--rdt-tw-* for Tailwind) instead.

📚 Documentation

Live demo and full docs for every feature, styling adapter and extension: react-datatables-net.ascentspark.com

In this repo:

Doc What's inside
Edit in place the editing engine in full
Security cell rendering and the XSS boundary
Changelog dated release notes

🔢 Versions

@ascentsparksoftware/react-datatables-net uses independent semantic versioning (unlike its Angular sibling, it does not need one release line per framework major). Peer dependencies: react / react-dom ^19 and datatables.net ^2.1; the datatables.net-dt and datatables.net-bs5 peers are optional — install the one your chosen styling adapter needs.

🛠️ Working on this repo

This is an npm workspace: package/ is the publishable library, demo/ is the Next.js demo app.

npm install

npm run build -w @ascentsparksoftware/react-datatables-net   # build the library
npm run dev -w demo                     # run the demo (http://localhost:4288)
npm test -w @ascentsparksoftware/react-datatables-net        # unit tests (Vitest)
npm run build:demo && npx playwright test   # end-to-end tests

🤝 Help keep it healthy

We genuinely try to keep this library current, bug-free and secure, and honestly the best way to get there is together. If something breaks, please open an issue so we can look into it. If you can fix a bug or add something useful, pull requests are very welcome, big or small. An open-source library stays dependable only when people use it, tell us what's broken, and pitch in now and then, so thank you in advance for anything you send our way. 💛

👋 About

We are Ascentspark. We have used datatables.net across our projects and kept writing the same wrapper code to make it feel at home in modern React. This is that code, tidied up and shared, in case you are looking for a React wrapper too.

License

MIT

About

A modern react wrapper over datatables.net plugin

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors