Shiny SelectInput and SelectizeInput

2019-05-31 02:44发布

问题:

I updated my Shiny library to version 1.1.0 and I noticed some very strange behavior with selectInput/selectizeInput and observeEvent/eventReactive.

  1. The problem occurs when I press backspace and clear the contents of the drop-down menu. In the previous Shiny version the backspace coupled with a eventReactive, the reactive expression wouldn't evaluate (I guess it treated this as a NULL) observe and reactive would evaluate which is desired.

  2. req() is also behaving weird, in Example 1 below if we press the backspace and clear the input, renderTable is triggered but when req(input$variable) is empty the table disappears. In the previous version if Shiny I believe the table would simply remain the same.

Reproducing Code:

Example 1

shinyApp(
  ui = fluidPage(
    selectizeInput("variable", "Variable:",
                c("Cylinders" = "cyl",
                  "Transmission" = "am",
                  "Gears" = "gear")),
    tableOutput("data")
  ),
  server = function(input, output) {
    observeEvent(input$variable,{
      cat("Printing: ",input$variable,"\n")
    })

    output$data <- renderTable({
      req(input$variable)
      Sys.sleep(2)
      mtcars[, c("mpg", input$variable), drop = FALSE]
    }, rownames = TRUE)
  }
)

or

Example 2

This looks like okay behavior but if you notice the renderTable is still being called when the backspace is pressed. If this was an expensive computation it would be undesirable behavior.

    shinyApp(
  ui = fluidPage(
    selectInput("variable", "Variable:",
                   c("Cylinders" = "cyl",
                     "Transmission" = "am",
                     "Gears" = "gear")),
    tableOutput("data")
  ),
  server = function(input, output) {
    observeEvent(input$variable,{
      cat("Printing: ",input$variable,"\n")
    })

    output$data <- renderTable({
      req(input$variable)
      Sys.sleep(2)
      mtcars[, c("mpg", input$variable), drop = FALSE]
    }, rownames = TRUE)
  }
)

My desired behavior: When the backspace is pressed to clear the menu observeEvents and eventReactive are not triggered.

回答1:

It seems like the current behavior triggers an event on backspace but the input value remains the same. This behavior could actually be an unintended change that happenend when the JavaScript function Shiny.onInputChange was updated. The NEWS on shinys github site claims the following under Version 1.1.

NEW FEATURES

[...]

Introduced two changes to the (undocumented but widely used) JavaScript function Shiny.onInputChange(name, value). First, we changed the function name to Shiny.setInputValue (but don't worry--the old function name will continue to work). Second, until now, all calls to Shiny.onInputChange(inputId, value) have been "deduplicated"; that is, anytime an input is set to the same value it already has, the set is ignored. With Shiny v1.1, you can now add an options object as the third parameter: Shiny.setInputValue("name", value, {priority: "event"}). When the priority option is set to "event", Shiny will always send the value and trigger reactivity, whether it is a duplicate or not.

The current version of selectInput seems to take advantage of this new {priority: "event"} option but that is just speculation.

Workaround

You can adapt your server code to correctly handle this new behavior by deduping the inputs yourself.

dedupedValue <- reactiveVal()
observe({ dedupedValue(input$value) })

Then you use dedupedValue() instead of input$value in the rest of your server code. This will also work with older versions of shiny.

NOTE: If you try to use reactive instead of observe in the above code it will not work.

Long term solution

Maybe it would be best to set this question on hold until the shiny devs took a look at your GitHub issue. As outlined above, the cause of this is probably an interface change on the JavaScript side of shiny. If this indeed created code breaking changes, I am sure the devs will provide a fix to ensure backwards compability.

About req

This is basically unrelated to the issue at hand but came up with your question: If you want req to retrain the old output if the condition is not "truthy", you should call it as

req(condition, cancelOuput = TRUE)