glin/reactable

Custom render function firing multiple times

dleopold opened this issue · 3 comments

I was debugging a custom render function for a table inside a shiny app and noticed that updates to the table cause the function to fire 3 x for each row on every render. The rendered table is correct, it just appears that the function is firing more times than it should need to. Not sure if this is a bug or just an unavoidable side effect of how the table is rendered. Here is a simple shiny app with a console.log() call added in the render function so you can see how many times it fires in the browser console.

library(shiny)
library(reactable)

ui <- fluidPage(
  reactableOutput("table"),
  actionButton("update", "Update table")
)

server <- function(input, output) {
  
  tabDat <- reactiveVal(iris[1:2,])
  
  output$table <- renderReactable({
    reactable(
      tabDat(),
      columns = list(
        Species = colDef(
          html = TRUE,
          cell = htmlwidgets::JS(
            "
            function(cellInfo) {
              console.log(cellInfo.value)
              return 'cellInfo.value';
            }
            "
          )
        )
      )
    )
  })
  observeEvent(input$update, {
    tabDat(iris[sample(1:nrow(iris), 2), ])
  })
}
shinyApp(ui = ui, server = server)
glin commented

This is just a technical limitation in react-table that allows some essential functionality to work, and is not real easy to avoid right now. Fortunately there are only a few cases where a table will render multiple times at once. Initial render is the worst at 3 times, but paginating, sorting, filtering, etc. usually just trigger one rerender.

Render functions aren't meant to be heavyweight or fire off their own side effects, so it's been fine for now, I think you've been the only one to notice. Maybe one day this could be improved? But it'd be a lot of effort to rework table internals, so not a priority right now.

edit: I forgot to mention, another mitigation for this would be to debounce consecutive custom render function calls so that 3 rapid fires get collapsed down to just 1. reactable is already doing this in some places to deal with the multiple rerendering limitation.

Thanks for the feedback. I am not sure I follow the method you suggest in you edit. Could you elaborate? How would you do that in the example in my post?

I was just doing some testing on this and noticed that (for this example at least) sorting causes the render function to fire 2x, but updating the data with updateReactable() actually causes it to fire 5x for each cell! That seems like a lot of unnecessary function calls. I attempted to debounce the function without any success so far.

library(shiny)
library(reactable)

ui <- fluidPage(
  reactableOutput("table"),
  actionButton("update", "Update table")
)

server <- function(input, output) {
  
  output$table <- renderReactable({
    reactable(
      iris[1:2,],
      columns = list(
        Species = colDef(
          html = TRUE,
          sortable = TRUE,
          cell = htmlwidgets::JS(
            "
            function(cellInfo) {
              console.log(cellInfo.value)
              return 'cellInfo.value';
            }
            "
          )
        )
      )
    )
  })
  observeEvent(input$update, {
    updateReactable(
      "table",
      data = iris[sample(1:nrow(iris), 2), ]
    )
  })
}
shinyApp(ui = ui, server = server)