Avoid temporary error messages in shiny outputs that are waiting on updateSelectInput


#1

I often use the updateSelectInput function in shiny to filter the choices in a select input whenever the selection of another related input changes. The code would look something like this:

observeEvent(input$filter1, {
  filter2_select <- input$filter2
  
  filter2_options <- data %>% 
    filter(Var1 == input$filter1) %>% 
    pull(Var2) %>% 
    unique()
  
  if(filter2_select %in% filter2_options) {
    updateSelectInput(session, "filter2", choices = filter2_options, selected = filter2_select)
  } else {
    updateSelectInput(session, "filter2", choices = filter2_options)
  }
})

This is useful to reduce the combination of filter options available to the user to only those for which there are data in a data set.

The resulting inputs are then used to filter the data which is then passed to a plot to visualise.

However I can't seem to get around the problem of getting the following error message appearing during the time input$filter2 is 'updating':

Warning: Error in $<-.data.frame: replacement has 1 row, data has 0

It will normally only appear for a fraction of a second which I'm assuming is the time the value in the updating input reverts to NULL then disappears when it has successfully updated it's selection and a new plot is rendered.

Is there away to avoid this and only have the plot re-render when the input has a valid selection and the data can be filtered without an error?

I've tried using req() and validate(need(...)) but they only solve the problem of the removing the error message, not the problem of the plot disappearing and then reappearing, however briefly.

Thanks,
Paul


#2

You can store your plot in a reactiveValues object, and then update the plot with an event.


#3

It sounds like you want both a reactive component and dynamic UI.

We'll use the built-in dataset mtcars. This is nice because the carb variable has a range of 4 1 2 3 6 8 and the variable gear has a range 4 3 5. And most importantly, when carb == 1, there are only gears 4 and 3.

Here's a reproducible example:

library(shiny)
library(tidyverse)

ui <- fluidPage(
  
  selectizeInput(
    inputId = "filter_carb",
    label = "Filter 1: carb",
    selected = 1,
    choices = unique(mtcars$carb)
  ),
  uiOutput("filter_gear"),
  mainPanel(
    verbatimTextOutput("table")
  )
)

server <- function(input, output) {
  
  filtered_data <- reactive({
    mtcars %>%
      filter(carb == input$filter_carb)
  })
  
  output$filter_gear <- renderUI({
    selectizeInput("filter_gear", "Filter 2: gear", unique(filtered_data()$gear))
  })
  
  output$table <- renderPrint({
    filtered_data() %>%
      filter(gear %in% input$filter_gear) %>% 
      print()
  })
}

shinyApp(ui = ui, server = server)

There are few key components. First, in server we have

  filtered_data <- reactive({
    mtcars %>%
      filter(carb == input$filter_carb)
  })

This means that every time input$filter_carb changes, filter_data will update with the new filtered dataset.

Next, we want to dynamically render an input UI based on our filtered data:

  output$filter_gear <- renderUI({
    selectizeInput("filter_gear", "Filter 2: gear", unique(filtered_data()$gear))
  })

This will be the gear filter selector, and will depend on the filtered_data. Note that it only uses the unique gear values found in the filtered data.

Then in our ui, we need to reference this dynamically created selector:

uiOutput("filter_gear")

This will behave like the other selector, but will update based on the carb filter.

Lastly, we can utilize this new filter like any other:

  output$table <- renderPrint({
    filtered_data() %>%
      filter(gear %in% input$filter_gear) %>% 
      print()
  })

#4

thanks for this detailed response @tmastny

Unfortunately it didn't solve my issue as rendering the input server-side with renderUI is essentially a substitute for updateSelectInput inside and observer as they both perform the same task of supplying new choice options for the input.

I think it may be because in my case I have several selectInputs that feed off each other in a hierarchical manner and so when when an input at the top of the hierarchy changes there is a delay in producing new filter choices for the input at the bottom of the hierarchy, hence the brief error.


#5

@paul, Did you figure out any neat way to deal with that problem? Maybe forcing a delay in the update?
I am running into a similar problem in my app and have some trouble solving it.


#6

The best I could come up with was using an actionButton with observeEvent to trigger a new plot rather than having it re-render every time an input changed. So the user would select the filters they want then hit an "Update" button to feed the new data to the plot.

Another option was using:

validate(
  need(nrow(df) > 0, message = FALSE)
)

inside the plot render function. This doesn't stop the plot disappearing briefly when there is no data but it does remove any error messages.

Let me know if you come up with anything better!