filtering action not occurring within eventReactive for dynamically-generated selectInput default choice

I am having trouble getting a table to pre-populate when it is encased within an eventReactive() command using eventReactive(..., ignoreNULL = F). The complications are:

  • my shiny app is using modules
  • a filtering step is being used within eventReactive() using a variable from a static selectInput() and a variable from a dynamically-generated selectInput()

The first variable will filter as expected, the second will not. The second variable is not detected as selected. My intent is when this app is initially launched, the table is generated using default values for the variables.

Here is a reprex:

### global conditions ####

library(dplyr); library(shiny); library(shinydashboard); library(DT)

dframe = data.frame(var1 = rep(c("AAA", "BBB", "CCC"), 10), 
                    var2 = sample(c("orange", "pink"), 30, replace = T), 
                    var3 = sample(1:100, 30))

### modular functions ####

extra_UI <- function(id) {
  ns = NS(id)

 tagList(selectInput(ns("VAR1"),
                     label = "select a variable",
                     choices = unique(dframe$var1),
                     selected = NULL),
         uiOutput(ns("conditional_menu")),
         uiOutput(ns("test_text")),
         br(), br(),
         actionButton(ns("go"), "Update Table"), 
         br(), br(),
         DT::dataTableOutput(ns("table"))
    )
}


extra <- function(input, output, session) {
  
  ns <- session$ns
  Var1 <- reactive(input$VAR1)

  output$conditional_menu <- renderUI({
    req(Var1())
    colors <-  dplyr::filter(dframe, var1 == Var1()) %>% distinct(var2) %>% pull(var2)
    selectInput(ns("VAR2"), "Select a second variable", colors)
  })

  Var2 <- reactive(input$VAR2)
  
  # output selections - both Var1 and Var2 have values assigned to them when app launches
  output$test_text <- renderUI({ 
    req(Var1(), Var2())
    paste("selected variables: ",  Var1(), Var2())
  })

  # output table = only Var1 registers as having a value when app launches
  x <- eventReactive(input$go, {
    dplyr::filter(dframe, var1 == Var1(), var2 == Var2())
  }, ignoreNULL = FALSE)  # table is produced after clicking the go button
  
  output$table <- renderDataTable({ x() })
}


##### normal UI and server functions ####

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
    fluidRow(
      extra_UI(id = "new_menu")
    )
  )
)

server <- function(input, output, session) {
  callModule(module = extra, id = "new_menu") 
}

shinyApp(ui = ui, server = server)

When the app is launched, both Var1 and Var2 will be displayed in a renderText() command, but the table is not produced. Instead there is this error for the filtering step:

Error: Problem with filter() input ..2.
e[31mxe[39m Input ..2 must be of size 30 or 1, not size 0.
e[34mℹe[39m Input ..2 is var2 == Var2().

Everything displays as intended once the action button is clicked.

Any suggestions on how to make "Var2" visible for the filtering step upon app initialisation? Thanks in advance.

(of course, in this example, there really is not a need to dynamically generate the conditional menu for var2, but it's good proxy for what I am actually trying to do).

1 Like

At the very least, just requiring VAR2 will prevent your error.

But that still means that the table doesn't show up until the user presses the go button, so it doesn't solve your full problem.

  x <- eventReactive(input$go, {
    req(input$VAR2)
    dplyr::filter(dframe, var1 == Var1(), var2 == Var2())
  }, ignoreNULL = FALSE)  #

To get the table to show up before the user presses go, it sounds like you need x() to be executed after output$conditional_menu runs. I'm still stumped on how to do that, so I'm looking forward to hearing what the solution is!

Leaving out the req() statement allows the error message to be visible, which I want at this time. The command x() is being executed after output$conditional is rendered in the UI, but there is seemingly conflicting evidence about what is happening to input$VAR2 upon app initialisation.

generally you only want the go button to update the table, but the first init is a special case, so i made a special arrangement for that.

Var2 <- reactive(input$VAR2)

  output$test_text <- renderUI({
    req(Var1(), Var2())
    paste("selected variables: ",  Var1(), Var2())
  })
  var2_init <- reactiveVal(0)
  observeEvent(Var2(),{
    if(var2_init()==0){  x <- eventReactive(input$go, {
    var2_init(1)
    dplyr::filter(dframe, var1 == Var1(), var2 == Var2())
  }, ignoreNULL = FALSE)  # table is produced after clicking the go button
  output$table <- renderDataTable({ x() })
    }
})
1 Like

Thanks, that gave me some ideas. This is rather cumbersome, so I don't think I want to implement it. My app has multiple conditional menus and resultant variables used for downstream actions - and there are many of those, not just one table.

I'm curious why Var2() could be seen for the text output, but not the table. If Var2() was a regular reactive object from selectInput and not the result of renderUI(..., selectInput()), this problem would not occur.

I tried running browser() per the Shiny debugging guidelines and my R session aborted (I've tried multiple times):

  x <- eventReactive(input$go, {
    browser()
    dplyr::filter(dframe, var1 == Var1(), var2 == Var2())
  }, ignoreNULL = FALSE) 

I was curious if renderUI() had anything to do with this - and it does not:

output$test_text2 <- renderPrint({ paste("my selections: ", Var1(), Var2()) }) in the server module function matched with h4(textOutput(ns("test_text2"))) in the ui module function produces the expected output for both variables on app initialisation.

This is a real puzzler for me. Why isn't the default selected value for Var2() being observed by the eventReaction() call? It does not matter if the default value is set manually within the the renderUI(..., selectInput()) call.

Unfortunately, it does.

Normally, when defining UI elements, Shiny will return the initial set right away. However, when using renderUI, Shiny will make two round trips. One for the initial set (such as input$var1) and later, a second set (such as input$var2 which is defined by the renderUI).

The bad news is that the data eventReactive() is being processed before the second trip is started.

Normally, I would say this is a perfect situation for shiny::freezeReactiveValue(input, "VAR2") (to be added in the renderUI(). But delaying values to be set until the button is pushed makes this difficult.


Since you do know some information going into the render UI, you can default Var2 to what the select value will be.

Updating extra to extract the colors to use both in the renderUI and Var2...

extra <- function(input, output, session) {
  
  ns <- session$ns
  Var1 <- reactive(input$VAR1)

### start updates
  colors <- reactive({
    req(Var1())
    dplyr::filter(dframe, var1 == Var1()) %>% distinct(var2) %>% pull(var2)
  })

  # Default Var2 to the first colors value
  Var2 <- reactive(input$VAR2 %||% isolate(colors()[1]))

  output$conditional_menu <- renderUI({
    selectInput(ns("VAR2"), "Select a second variable", colors())
  })
### end updates

  # output selections - both Var1 and Var2 have values assigned to them when app launches
  output$test_text <- renderUI({ 
    req(Var1(), Var2())
    paste("selected variables: ",  Var1(), Var2())
  })

  # output table = only Var1 registers as having a value when app launches
  x <- eventReactive(input$go, {
    dplyr::filter(dframe, var1 == Var1(), var2 == Var2())
  }, ignoreNULL = FALSE)  # table is produced after clicking the go button
  
  output$table <- renderDataTable({ x() })
}
2 Likes

This is fantastic, thank you.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.