Skip to content

Commit 0a3888b

Browse files
committed
Fix selection bugs in backend modes and clean up server interface
Selection fixes: - Fix isAllRowsSelected reporting true when only current page rows are selected in backend modes by checking against serverRowCount - Fix deselect-all overwriting all selections instead of only removing filtered rows, using Set-based merge/remove logic - Add toggleAllInProgressRef guard against concurrent async toggleAllRowsSelected calls from checkbox onChange+onClick bubbling - Fix .selection column included in DuckDB-WASM global search SQL by adding searchable=FALSE to its colDef - Fix defaultSelected highlighting wrong rows when defaultSorted reorders pre-rendered data by including __state in pre-rendered first page - Fix data prop type warning for __state (object of arrays, not array) Server interface cleanup: - Remove unused selectedRowIds param from all backend signatures and server data fetch - Add shared virtual column constants (selectionColumnId, detailsColumnId, virtualColumnIds) in R/utils.R - Use constants in reactable.R and backend-duckdb.R Tests and test docs: - Add JS tests for isAllRowsSelected, search exclusion, defaultSelected with __state, deselect-all filtered, select-all queries, sort/filter persistence - Add DuckDB and V8 selection test Rmd files with Shiny examples including updateReactable action buttons
1 parent cd449c7 commit 0a3888b

12 files changed

Lines changed: 839 additions & 55 deletions

File tree

R/backend-df.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ reactableServerData.reactable_backendDf <- function(
3030
pagination = NULL,
3131
paginateSubRows = NULL,
3232
selectAll = NULL,
33-
# Unused/unimplemented props
34-
selectedRowIds = NULL,
3533
expanded = NULL,
3634
...
3735
) {

R/backend-duckdb.R

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ reactableServerInit.reactable_backendDuckdb <- function(x, data = NULL, columns
109109
# Zero-copy registration - DuckDB reads directly from R data frame memory
110110
duckdb::duckdb_register(con, "reactable_data", data)
111111
x$private$con <- con
112-
# Filter out virtual columns (e.g., .selection) that don't exist in the data
113-
x$private$columns <- Filter(function(col) !startsWith(col$id, "."), columns)
112+
# Filter out virtual columns that don't exist in the data
113+
x$private$columns <- Filter(function(col) !(col$id %in% virtualColumnIds), columns)
114114
}
115115

116116
#' @exportS3Method
@@ -128,8 +128,6 @@ reactableServerData.reactable_backendDuckdb <- function(
128128
pagination = NULL,
129129
paginateSubRows = NULL,
130130
selectAll = NULL,
131-
# Unused/unimplemented props
132-
selectedRowIds = NULL,
133131
expanded = NULL,
134132
...
135133
) {

R/backend-v8.R

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ Do you need to run `install.packages("V8")`?', call. = FALSE)
6464
filters = NULL,
6565
searchValue = NULL,
6666
groupBy = NULL,
67-
# TODO currently unused/unimplemented props
68-
selectedRowIds = NULL,
6967
expanded = NULL,
7068
...
7169
) {
@@ -77,8 +75,6 @@ Do you need to run `install.packages("V8")`?', call. = FALSE)
7775
searchValue = searchValue,
7876
sortBy = sortBy,
7977
groupBy = asJSONList(groupBy),
80-
# Currently unused
81-
selectedRowIds = selectedRowIds,
8278
expanded = expanded
8379
))
8480

R/reactable.R

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ reactable <- function(
367367
columnKeys <- columnKeys[columnKeys != subRowsKey]
368368

369369
if (!is.null(details)) {
370-
detailsKey <- ".details"
370+
detailsKey <- detailsColumnId
371371
columnKeys <- c(detailsKey, columnKeys)
372372
detailsColumn <- colDef(name = "", sortable = FALSE, filterable = FALSE,
373373
searchable = FALSE, resizable = FALSE, width = 45,
@@ -382,9 +382,9 @@ reactable <- function(
382382
}
383383

384384
if (!is.null(selection)) {
385-
selectionKey <- ".selection"
385+
selectionKey <- selectionColumnId
386386
columnKeys <- c(selectionKey, columnKeys)
387-
selectionColumn <- colDef(name = "", resizable = FALSE, width = 45)
387+
selectionColumn <- colDef(name = "", searchable = FALSE, resizable = FALSE, width = 45)
388388
selectionColumn$selectable <- TRUE
389389
if (selectionKey %in% names(columns)) {
390390
selectionColumn <- mergeLists(selectionColumn, columns[[selectionKey]])
@@ -749,7 +749,7 @@ reactable <- function(
749749
sortBy = defaultSorted,
750750
groupBy = groupBy,
751751
searchMethod = searchMethod
752-
# TODO add expanded, selectedRowIds
752+
# TODO add expanded
753753
)
754754

755755
do.call(reactableServerInit, c(list(backend), initialProps))
@@ -904,6 +904,14 @@ reactable <- function(
904904
firstPageData <- firstPageData[do.call(order, orderArgs), , drop = FALSE]
905905
}
906906
firstPageData <- utils::head(firstPageData, defaultPageSize)
907+
# Convert _reactable_rowid into __state for stable row identification on the
908+
# pre-rendered page. Without this, rows use page-relative indices as IDs, which
909+
# causes defaultSelected to highlight wrong rows when defaultSorted reorders data.
910+
rowids <- firstPageData[["_reactable_rowid"]]
911+
firstPageData[["__state"]] <- dataFrame(
912+
id = as.character(rowids),
913+
index = as.integer(rowids)
914+
)
907915
firstPageData[["_reactable_rowid"]] <- NULL
908916
data <- toJSON(firstPageData)
909917
serverRowCount <- totalRowCount

R/shiny.R

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,6 @@ getReactableState <- function(outputId, name = NULL, session = NULL) {
364364
#' @param pagination Whether pagination is enabled, `TRUE` or `FALSE`.
365365
#' @param paginateSubRows Whether sub rows are paginated, `TRUE` or `FALSE`.
366366
#' @param expanded The current expanded rows.
367-
#' @param selectedRowIds The current selected rows.
368367
#' @param ... Additional arguments passed to the S3 method.
369368
#' @return
370369
#' - `reactableServerData()` should return a [resolvedData()] object.
@@ -386,7 +385,6 @@ reactableServerInit <- function(
386385
groupBy = NULL,
387386
pagination = NULL,
388387
paginateSubRows = NULL,
389-
selectedRowIds = NULL,
390388
expanded = NULL,
391389
...
392390
) {
@@ -409,7 +407,6 @@ reactableServerData <- function(
409407
groupBy = NULL,
410408
pagination = NULL,
411409
paginateSubRows = NULL,
412-
selectedRowIds = NULL,
413410
expanded = NULL,
414411
...
415412
) {

R/utils.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
#' @export
33
htmlwidgets::JS
44

5+
# Virtual column names used internally by reactable. These columns are added
6+
# to the column list for selection/details UI but don't exist in user data.
7+
selectionColumnId <- ".selection"
8+
detailsColumnId <- ".details"
9+
virtualColumnIds <- c(selectionColumnId, detailsColumnId)
10+
511
# Serialize a data frame to Arrow IPC stream format, base64-encoded.
612
# Used by backendDuckDB() to send data to the browser for DuckDB-WASM ingestion.
713
serializeArrowIPC <- function(data) {

0 commit comments

Comments
 (0)