In my quest for interesting health data, I stumbled upon the National Immunization Survey—Child 2014 raw data files made public by the CDC. I wished I had a quick way of visualizing some of the variables. Since I don’t have access to any fancy software, I decided to build my own quick data explorer. Like the rest of my apps, this was built as an exercise to learn R and Shiny.

The challenges in building the app were all tied to the quirks and limitations of Shiny and some of the other packages I decided to use.

Shiny keeps removed input objects

I used Shiny’s insertUI() and removeUI() functions to dynamically add input objects. According to Shiny, “the UI generated with insertUI() is persistent: once it’s created, it stays there until removed by removeUI().” However, removeUI() only removes the object from the dom not from Shiny’s input list. This is documented here and here. When you run the following example, you can see that removeUI does not remove the input object.

library(shiny)

ui <- basicPage(
  actionButton("add", "add ui"),
  actionButton("remove", "remove ui"),
  div(id = "dynamic_ui"),
  verbatimTextOutput("text")
)

server <- function(input, output) {
  observeEvent(input$add, {
    insertUI(
      selector = "#dynamic_ui",
      ui = actionButton("new_button", "new button")
    )
  })
  observeEvent(input$remove, {
    removeUI(
      selector = "#new_button"
    )
  })
  output$text <- renderPrint({
    names(input)
  })
}

shinyApp(ui = ui, server = server)

The workarounds are very hacky, they depend on access to what I think are the “internals” of Shiny by using .subset2(). I opted for not messing with it, which means I end up with hundreds of unused input objects in a session.

Limited DT Package selection options

The DT package is full of features and I think it provides one of the best options to dynamically interact with tables in Shiny. I’m using DT’s selection feature in my app and wanted to match the color of the selected row to my theme’s primary color.

In DT tables, selection can be enabled through the selection argument or through the extension. The following example uses the selection argument to pre-select the first row and enable multiple row selection. This method does not allow css class modification.

datatable(df,
          style = "bootstrap",
          selection = list(mode = "multiple", selected = 1)
)

By contrast, the css class can be modified when using the extension.

datatable(df,
          style = "bootstrap",
          selection = "none",
          extensions = "Select",
          options = list(
            select = list(className = "info", items = "row"))
          )

Importantly, when you use the extension, you loose the ability to pre-select. Furthermore, the DT package for R warns against using the extension: “Note that DT has its own selection implementation and doesn’t use the Select extension because the latter doesn’t support the server-side processing mode well.”

I decided not to use the extension and instead use the bslib package and Bootstrap’s root css variables to match the selected row’s color to the theme’s primary color.

library(shiny)
library(DT)

ui <- fluidPage(
  theme = bslib::bs_theme(version = 5, bootswatch = "yeti"),
  tags$style(".table.dataTable tbody td.active, 
              .table.dataTable tbody tr.active td {
               background-color: var(--bs-primary)!important;}"),
  DTOutput("tbl")
)

server <- function(input, output) {
  output$tbl <- renderDT({
    df <- data.frame(V1 = rnorm(5), V2 = rnorm(5, 20, 5))
    
    datatable(df,
              style = "auto",
              selection = list(mode = "multiple", selected = 1)
    )
  })
}

shinyApp(ui = ui, server = server)

Although this solution has the bonus of upgrading to bootstrap 5, it suffers from having to edit the css “by hand”. The ability to modify the css class via DT’s function would be better. You can see the initial solution with a working example at StackOverflow.


2022 Ingrid Lagos