Skip to content

feat(headless): add useDataTable hook#8868

Open
alexcarpenter wants to merge 6 commits into
mainfrom
carp/use-data-table
Open

feat(headless): add useDataTable hook#8868
alexcarpenter wants to merge 6 commits into
mainfrom
carp/use-data-table

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds useDataTable hook to @clerk/headless for managing server-driven data table state
  • Manages sorting, column filters, global filter, pagination, and row selection
  • Data passes through unchanged — consumers wire onChange callbacks to their own fetch layer
  • Controlled/uncontrolled support for all state slices via useControllableState
  • TanStack Table-compatible state shapes (SortingState, ColumnFiltersState, PaginationState) and Updater<T> functional-update protocol

Usage

const table = useDataTable({
  data: members,        // current page from server
  totalCount: 523,      // for pagination math
  defaultPagination: { pageIndex: 0, pageSize: 10 },
  onSortingChange: s => refetch({ sorting: s }),
  onColumnFiltersChange: cf => refetch({ columnFilters: cf }),
  onGlobalFilterChange: gf => refetch({ globalFilter: gf }),
  onPaginationChange: p => refetch({ pagination: p }),
})

// table.rows, table.setSorting, table.setGlobalFilter,
// table.nextPage, table.getPageCount, table.toggleAllRowsSelected, ...

Test plan

  • 41 unit tests covering all state slices, pagination helpers, and row selection
  • pnpm test --filter @clerk/headless passes

Summary by CodeRabbit

  • New Features
    • Introduced a new useDataTable hook for data table state management, including sorting, column/global filtering, pagination, and row selection with controllable or default state.
    • Added TypeScript types related to useDataTable to improve editor support and integration.
  • Documentation
    • Added useDataTable documentation to the hooks docs and Storybook, including options, return values, and key types.
  • Tests
    • Added a comprehensive useDataTable test suite covering row enrichment/stable IDs, controlled updates for sorting, filters, pagination, and row selection.

Adds a server-driven data table state hook to @clerk/headless with
sorting, column/global filtering, pagination, and row selection. Data
passes through unchanged — consumers wire onChange callbacks to their
own fetch layer.
@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0738664

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 03a85372-34e4-45cd-b910-357a57e7b26a

📥 Commits

Reviewing files that changed from the base of the PR and between 9766957 and d3baa30.

📒 Files selected for processing (5)
  • packages/headless/src/hooks/use-data-table.ts
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/stories/use-data-table.mdx
  • packages/swingset/src/stories/use-data-table.stories.tsx
✅ Files skipped from review due to trivial changes (2)
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/stories/use-data-table.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/headless/src/hooks/use-data-table.ts

📝 Walkthrough

Walkthrough

A new useDataTable React hook is added to packages/headless. It manages sorting, column filters, global filter, pagination, and row selection state via useControllableState, with updater-aware setters, memoized row derivation, and pagination/selection helpers. Public types and interfaces are exported from the hooks barrel, and a full Vitest test suite is included. Storybook stories and MDX documentation are registered in the swingset package.

Changes

useDataTable Hook

Layer / File(s) Summary
Public types, interfaces, and barrel exports
packages/headless/src/hooks/use-data-table.ts, packages/headless/src/hooks/index.ts, .changeset/hip-ghosts-notice.md
Declares Updater, OnChangeFn, state-shape types (SortingState, ColumnFiltersState, PaginationState, RowSelectionState), and interfaces (UseDataTableOptions, DataTableRow, UseDataTableReturn). Re-exports all from the hooks barrel. Adds the changeset entry.
Controllable state slices and updater-aware setters
packages/headless/src/hooks/use-data-table.ts
Wires five useControllableState slices for sorting, column filters, global filter, pagination, and row selection with optional defaults and on*Change callbacks. Implements updater-aware public setters that interpret Updater<T> (direct value or functional updater) for each slice.
Rows derivation with per-row selection helpers
packages/headless/src/hooks/use-data-table.ts
Derives rows from opts.data using useMemo, assigns stable string id via opts.getRowId or index fallback, and exposes per-row getIsSelected and toggleSelected backed by rowSelection updates.
Pagination and selection helpers, return object
packages/headless/src/hooks/use-data-table.ts
Implements pagination helpers (getPageCount, getCanNextPage/getCanPreviousPage, nextPage/previousPage, setPageSize), all-rows selection helpers (getIsAllRowsSelected, getIsSomeRowsSelected, toggleAllRowsSelected), and assembles the return object with all state, setters, rows, and helpers.
Test suite
packages/headless/src/hooks/use-data-table.test.ts
Covers rows enrichment (stable ids, original, data changes), sorting (defaults, value/functional updater, controlled mode), column filters, global filter, pagination (page count, navigation, size reset, onPaginationChange), and row selection (per-row toggle, all/some helpers, toggleAllRowsSelected, onRowSelectionChange).
Storybook stories and documentation registration
packages/swingset/src/stories/use-data-table.stories.tsx, packages/swingset/src/stories/use-data-table.mdx, packages/swingset/src/lib/registry.ts, packages/swingset/src/components/DocsViewer.tsx
Defines Storybook story meta for the hook. Adds comprehensive MDX documentation with usage examples, options/return API tables, and TypeScript type definitions. Registers the story and documentation entries in the swingset registry and docs viewer module map.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • clerk/javascript#8819: Both PRs update the Swingset story registry and DocsViewer module registration to add new hook components with documentation and stories.

Suggested reviewers

  • prestonwebdev

Poem

🐇 Hoppin' through rows with a flick of my paw,
Sorting and filtering without a flaw!
Each page turns swift, selection stays neat,
Controllable state — oh, what a treat!
From index to hook, the exports align,
This data table bunny is doing just fine! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(headless): add useDataTable hook' clearly and concisely summarizes the main change—adding a new useDataTable hook to the headless package.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 15, 2026 9:17pm
swingset Ready Ready Preview, Comment Jun 15, 2026 9:17pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jun 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8868

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8868

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8868

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8868

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8868

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8868

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8868

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8868

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8868

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8868

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8868

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8868

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8868

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8868

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8868

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8868

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8868

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8868

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8868

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8868

commit: 0738664

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
packages/headless/src/hooks/use-data-table.test.ts (1)

100-370: ⚡ Quick win

Add controlled-mode tests for the other controllable slices.

sorting has a controlled-state test, but columnFilters, globalFilter, pagination, and rowSelection do not. Since controlled/uncontrolled behavior is a core hook capability, add parity tests for those slices to prevent contract drift.

As per coding guidelines, "**/*.{test,spec}.{ts,tsx}: Unit tests are required for all new functionality."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/headless/src/hooks/use-data-table.test.ts` around lines 100 - 370,
Add controlled-mode tests for the columnFilters, globalFilter, pagination, and
rowSelection slices to establish parity with the existing controlled-state test
for sorting. For each slice, create a test that verifies when the slice value is
provided as a prop (e.g., columnFilters={[...]}, globalFilter="value",
pagination={...}, rowSelection={...}), the hook respects that externally-managed
state and only fires the corresponding callback (onColumnFiltersChange,
onGlobalFilterChange, onPaginationChange, onRowSelectionChange) without
internally overriding the value. This ensures the hook properly supports both
controlled and uncontrolled patterns for all controllable slices.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/hip-ghosts-notice.md:
- Around line 1-2: The changeset file hip-ghosts-notice.md is empty and needs to
be populated with proper release metadata and changelog information. Add the
appropriate package bump type (major, minor, or patch) for `@clerk/headless` and
include a concise changelog summary describing the new public API being
introduced in this PR. Follow the standard changeset format to ensure the
feature includes proper version management and release notes when shipped.

In `@packages/headless/src/hooks/index.ts`:
- Around line 2-13: Remove the re-exports of useDataTable function and related
types (ColumnFiltersState, DataTableRow, OnChangeFn, PaginationState,
RowSelectionState, SortingState, Updater, UseDataTableOptions,
UseDataTableReturn) from the barrel index.ts file. Instead, consumers should
import these directly from the './use-data-table' module to avoid expanding the
barrel and reduce circular-dependency risk as per the repo guidelines.

In `@packages/headless/src/hooks/use-data-table.test.ts`:
- Around line 192-252: The existing pagination tests verify that getCanNextPage
and getCanPreviousPage return false at boundaries, but they do not test what
happens when nextPage or previousPage are actually called at those boundaries.
Add two new test cases: one that calls nextPage() when already on the last page
(totalCount equals data length multiplied by pageSize) and asserts that
pageIndex remains unchanged, and another that calls previousPage() when on page
0 and asserts that pageIndex remains 0. These tests will verify that the
navigation methods properly guard against out-of-range pageIndex values.

In `@packages/headless/src/hooks/use-data-table.ts`:
- Around line 6-78: Add comprehensive JSDoc comments to all public API exports
in this file to meet the documentation requirements for public-facing APIs.
Specifically, document the type definitions (Updater, OnChangeFn, SortingState,
ColumnFiltersState, PaginationState, RowSelectionState), interfaces
(UseDataTableOptions, DataTableRow, UseDataTableReturn), and the useDataTable
function. Each JSDoc should include clear descriptions of parameters, return
types, and behavior where applicable to support generated documentation.
- Line 184: The getIsSomeRowsSelected function on line 184 incorrectly returns
true whenever any rows are selected, including when all rows are selected. To
properly support indeterminate checkbox state, this function should only return
true for partial selection (some but not all rows selected). Modify the logic to
check that the number of selected rows is greater than zero AND less than the
total number of rows available in the rows array, ensuring it returns false when
either no rows are selected or all rows are selected.
- Around line 142-149: The row ID generation in the opts.data.map function uses
the array index (i) to create unstable selection keys that change when data is
sorted or paginated on the server. Replace the index-based id derivation with a
stable identifier from the original data object (such as a unique ID property
that persists across pages and sorts). This ensures that row selection remains
correct when the server returns data in a different order or on a different
page.
- Around line 168-174: The previousPage and setPageSize callbacks in
use-data-table.ts need input validation to prevent invalid pagination state. In
the previousPage callback, ensure pageIndex never goes below 0 by clamping the
decremented value. In the setPageSize callback, validate that the size parameter
is a positive number greater than 0 before updating pagination state. These
validations will prevent Line 157 from producing invalid Infinity or negative
counts due to invalid pageIndex or pageSize values.

---

Nitpick comments:
In `@packages/headless/src/hooks/use-data-table.test.ts`:
- Around line 100-370: Add controlled-mode tests for the columnFilters,
globalFilter, pagination, and rowSelection slices to establish parity with the
existing controlled-state test for sorting. For each slice, create a test that
verifies when the slice value is provided as a prop (e.g.,
columnFilters={[...]}, globalFilter="value", pagination={...},
rowSelection={...}), the hook respects that externally-managed state and only
fires the corresponding callback (onColumnFiltersChange, onGlobalFilterChange,
onPaginationChange, onRowSelectionChange) without internally overriding the
value. This ensures the hook properly supports both controlled and uncontrolled
patterns for all controllable slices.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: bb6dcb2b-627d-4ce4-8135-3e92de0c8ff5

📥 Commits

Reviewing files that changed from the base of the PR and between ed2cf75 and 42b8e73.

📒 Files selected for processing (4)
  • .changeset/hip-ghosts-notice.md
  • packages/headless/src/hooks/index.ts
  • packages/headless/src/hooks/use-data-table.test.ts
  • packages/headless/src/hooks/use-data-table.ts

Comment thread .changeset/hip-ghosts-notice.md
Comment thread packages/headless/src/hooks/index.ts
Comment thread packages/headless/src/hooks/use-data-table.test.ts
Comment thread packages/headless/src/hooks/use-data-table.ts
Comment thread packages/headless/src/hooks/use-data-table.ts
Comment thread packages/headless/src/hooks/use-data-table.ts Outdated
Comment thread packages/headless/src/hooks/use-data-table.ts Outdated
- add getRowId option for stable row IDs across server-driven pages/sorts
- clamp nextPage to last page, previousPage to 0, guard setPageSize > 0
- fix getIsSomeRowsSelected to return false when all rows selected
- add boundary tests for nextPage/previousPage at limits
- add controlled-mode tests for columnFilters, globalFilter, pagination, rowSelection
- pass onChange callbacks directly (avoids non-null assertions, OnChangeFn<T> is assignable to (value: T) => void via contravariance)
- destructure getRowId before useMemo to satisfy react-hooks/exhaustive-deps
- combine type and value imports from use-data-table to fix simple-import-sort
@vercel vercel Bot temporarily deployed to Preview – clerk-js-sandbox June 15, 2026 20:57 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant