Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R Shiny and DataTables creating an alert when data is updated, but has the same number of columns as previous data

Tags:

r

dt

shiny

I'm writing an app that creates certain reports at the click of a button. When a new report has the same number of columns as the previous one, DT throws an alert about how column name from old data is not present in new data, that needs to be clicked through. After that, it creates the correct table without issue! This obviously makes for poor UX since it seems something has gone wrong when, in fact, everything went OK.

I'm pretty sure the issue is that the DTOutput() function is a part of a renderUI() call. It doesn't seem likely I can move away from this since I need to make some checks before displaying or hiding the table. Here is a simplified version as a reproducible example:

library(shiny)
library(DT)

ui <- fluidPage(
  actionButton("change_col", "Change column names"),
  actionButton("hide_table", "Hide/Show table"),
  uiOutput("custom_ui")
)


server <- function(input, output, session) {
  output$custom_ui <- renderUI({
    # this req() simulates that my app needs to check some reactive values to
    # determine whether to show or hide the table
    req(input$change_col)
    
    # The UI programatically decides whether to show the table.
    if (input$hide_table %% 2 == 1) return(h2("NO DATA"))
    
    DT::DTOutput("my_table")
  })

  
  output$my_table <- renderDT({
    req(input$change_col)
    my_data <- iris

    # the number of columns is always the same, 
    # but the column names differ each run
    new_colnames <- paste(colnames(iris)[1:5], 
                          rnorm(5)
                          )
    
    
    
    colnames(my_data)[1:5] <- new_colnames

    DT::datatable(my_data,
                  escape = F,
                  selection = "none",
                  rownames = FALSE,
                  options = list(
                    dom = 'tp',
                    ordering = F,
                    language = list(zeroRecords = "Nema podataka za prikaz",
                                    paginate = list('next' =  "Sljedeća",
                                                    'previous' = "Prethodna")
                    ),
                    scrollX = TRUE,
                    searching = F)
    )
  })
}

shinyApp(ui, server)

To reproduce, click on Change column names, Click on Hide/Show twice, and then Change column names again.

Do you have any suggestions as how to avoid this alert?

One solution I've found is to add a callback that disables DT alerts (adding callback = DT::JS("$.fn.dataTable.ext.errMode = 'none';") to the datatable() call). I'm not happy with that since if there were any legitimate alerts, this would disable them too.

like image 435
mkranj Avatar asked Nov 03 '25 22:11

mkranj


1 Answers

While I would suggest conditionalPanel here, I want to explain why the error occurs. The main issue is that you use DT::DTOutput("my_table") within your renderUI

  1. If you hide your table away, the DT::DTOutput("my_table") is still referencing the old table that does not exist anymore because you destroyed it using input$hide_table
  2. Then you show the table again, your DT has a new wrapper number, so it is in fact a new instance! But the reference to the old table is still set. So if you trigger the columns to be renamed, it tries to rename the cols of the old table instance, which does not exist anymore -> this is also why it already fails to find the first column Sepal.length.

So you need to return NULL within DTOutput when the button says so and also move your DT::DTOutput("my_table") directly into the UI.

library(shiny)
library(DT)

ui <- fluidPage(
  actionButton("change_col", "Change column names"),
  actionButton("hide_table", "Hide/Show table"),
  uiOutput("custom_ui"),
  DT::DTOutput("my_table")
)

server <- function(input, output, session) {
  
  output$custom_ui <- renderUI({
    req(input$change_col)
    if (input$hide_table %% 2 == 1) {
      h2("NO DATA")
    } else {
      NULL  # Don't render anything when table should be shown
    }
  })
  
  output$my_table <- renderDT({
    req(input$change_col)
    
    # hide table when button indicates it should be hidden
    if (input$hide_table %% 2 == 1) return(NULL)
    
    # the number of columns is always the same,
    # but the column names differ each run
    my_data <- iris
    colnames(my_data)[1:5] <- paste(colnames(iris)[1:5], rnorm(5))
    
    DT::datatable(
      my_data,
      escape = FALSE,
      selection = "none",
      rownames = FALSE,
      options = list(
        dom = 'tp',
        ordering = FALSE,
        language = list(
          zeroRecords = "Nema podataka za prikaz",
          paginate = list('next' =  "Sljedeća", 'previous' = "Prethodna")
        ),
        scrollX = TRUE,
        searching = FALSE
      )
    )
  })
}

shinyApp(ui, server)
like image 77
Tim G Avatar answered Nov 05 '25 13:11

Tim G



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!