Skip to content

Add opt-in lazy row details for Shiny - #428

Open
gkroken wants to merge 12 commits into
glin:mainfrom
gkroken:native-lazy-details
Open

gkroken wants to merge 12 commits into
glin:mainfrom
gkroken:native-lazy-details

Conversation

@gkroken

@gkroken gkroken commented Sep 15, 2026

Copy link
Copy Markdown

R details callbacks currently run for every row while a table is constructed, including rows whose details are never
opened. This adds lazyDetails = TRUE to defer those callbacks until expansion. Existing behavior remains eager by default.

Each opening captures a fresh snapshot. Reactive changes do not recalculate it; sorting, paging, and virtual scrolling
retain it while expanded. Closing discards the result and recursively releases nested lazy tables and Shiny bindings.
Reopening runs the callback again, with the existing one-based row index and column-name arguments.

The implementation uses a session registry, batched JSON requests, and unique table/panel identities. Rerendering
invalidates old registrations even when data is unchanged. A dedicated output binding delegates HTML and dependency
rendering to Shiny, including cleanup when dependencies finish after collapse and widget initialization after an
offscreen panel returns. Existing keyboard expansion and aria-expanded are retained; loading uses a status and busy state.

Limitations:

  • Requires a live Shiny session, with Shiny >= 1.5.0.
  • Replace data by rerendering. updateReactable(data = ...) and JavaScript setData() reject lazy callback tables;
    state-only updates remain available.
  • Server-side processing, static rendering, and caching rendered lazy widgets with bindCache() are unsupported.
  • Nested lazy tables must be returned from lazy callbacks. Eager details and cells reject embedded lazy tables.
  • All eligible rows have expanders, including callbacks that eventually return NULL.
  • Callbacks execute synchronously in R; arbitrary observers created by application callbacks remain app-owned.

Validation:

  • Full R suite: 980 assertions passed, including 51 lazy-details assertions.
  • JavaScript: 455 tests and 9 snapshots passed.
  • Shiny 1.5.0: 49 applicable lazy-details assertions passed; the eager-cache test requires Shiny >= 1.6.0 and is skipped.
  • Chromium: three-level nesting, 36 leaf panels, reopening, and rerendering passed on Shiny 1.5.0 and 1.14.0.
  • Delayed-dependency browser regressions passed for collapse cleanup, reopening, offscreen initialization, and keyboard/ARIA states.
  • The 10,000-row lazy dashboard passed virtual-retention, nested-cleanup, data-rerender, and benchmark-cleanup acceptance.
  • Production client/server builds, relevant ESLint and Prettier checks, and devtools::document() completed.

The small development fixtures under design/lazy-details/ cover nesting and delayed-dependency regressions and are
excluded from the built R package. The larger dashboard, benchmark, and dashboard acceptance script are excluded from
this PR. Earlier 10,000-row lazy acceptance results are supplemental review evidence; no performance claim is made here.

@gkroken

gkroken commented Sep 15, 2026

Copy link
Copy Markdown
Author

Note: this is entirely AI-coded, but the functionality it provides is immense. I started with a wrapper around reactable that has worked well for my org, so I figured it might be of use in the base package.

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