glin / reactable

Interactive data tables for R
https://glin.github.io/reactable
Other
612 stars 79 forks source link

Combining 'selection' with cell formatting function in a module results in an error #300

Open domsle opened 1 year ago

domsle commented 1 year ago

Hello,

When using the following code:

library(shiny)
library(reactable)

testUI <- function(id) {
  ns <- NS(id)
  tagList(
    fluidPage(
      titlePanel("row selection example"),
      reactableOutput(ns("table")),
      verbatimTextOutput(ns("selected"))
    )
  )
}

testServer <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 ns <- session$ns

                 selected <- reactive(getReactableState("table", "selected"))

                 output$table <- renderReactable({
                   reactable(iris, 
                             selection = "multiple", 
                             onClick = "select" ,
                             defaultPageSize = 10,
                             defaultColDef = colDef(
                               cell = function(x){ifelse(!is.na(x), format(x, digits = 3, nsmall = 3), "")},
                               filterable = TRUE
                             ) 
                   )
                 })

                 output$selected <- renderPrint({
                   print(selected())
                 })

                 observe({
                   print(iris[selected(), ])
                 })

               })
}

ui <- fluidPage(
  testUI('test')
)
server <- function(input, output, session) {
  testServer('test')
}
shinyApp(ui, server)

The result is that table does not appear at all. I get the following error in Chrome's console: image

I use the latest CRAN library (0.4.1) with R 4.1.2. Are you aware of this problem?

glin commented 1 year ago

Hi, thanks for the report, looks like there are at least two issues going on here. One of which is definitely a bug, and the other is possibly a bug, but maybe also partially-intended behavior that should be documented:

  1. When row selection is enabled, a new column without data is automatically added to the table for the selection checkboxes. This lets you customize a few things like the column width or cell styling, but the cell render function essentially does nothing because it's overridden by the selection checkbox/radio. When using defaultColDef, the selection column is supposed to be included, but I guess it doesn't make sense to apply the cell render function to it as well right now. However, it's probable that one day you'd be able to customize the selection cells using the cell renderer of the selection column, so excluding it in defaultColDef may not make sense either. At least for now, the selection column is documented, but we could add an additional warning that anything in defaultColDef will apply to the selection column as well.
  2. There's a bug with rendering multi-length vectors. In the table, it's triggered by trying to render logical(0), but you can also reproduce it minimally with:
    
    reactable(data.frame(x = TRUE), columns = list(x = colDef(
    cell = function() logical(0)
    )))

reactable(data.frame(x = TRUE), columns = list(x = colDef( cell = function() c(1, 2) )))


With 1 and 2 combined, the errors are happening because the default cell render function is applying to the empty `.selection` column, and producing `logical(0)` results because the selection column values are all `NULL`, and `is.na(NULL)` produces `logical(0)` (weird, but that's R).

For now, you can work around this by adding a `NULL` check to your default `cell` render function:
```r
defaultColDef = colDef(
  cell = function(x) {
    if (is.null(x)) return()
    ifelse(!is.na(x), format(x, digits = 3, nsmall = 3), "")
  },
  filterable = TRUE
)

or if you want to do it in the ifelse(), this works too, but is kind of odd to read:

defaultColDef = colDef(
  cell = function(x) {
    ifelse(isTRUE(!is.na(x)), format(x, digits = 3, nsmall = 3), "")
  },
  filterable = TRUE
)