rstudio / shiny

Easy interactive web applications with R
https://shiny.posit.co/
Other
5.37k stars 1.87k forks source link

updateSelectizeInput Triggers Reactive Chain Twice if server = TRUE #3437

Open mnist91 opened 3 years ago

mnist91 commented 3 years ago

Bug Description

updateSelectizeInput() does change its updated value twice if server = TRUE. First, it sets the selected value to "" and afterwards updates the value. This causes the reactive chain to trigger twice.

You can see this behavior by changing "Input 1" and checking the difference in the print-result whether the checkbox is ticked or not. There should not be an empty Input 2 in my opinion.

Example application


library(shiny)
set.seed(123)

# Define UI for application that draws a histogram
ui <- fluidPage(

  # Application title
  titlePanel("updateSelectizeInput triggers twice"),

  # main UI
  mainPanel(
    checkboxInput(inputId = "use_server",
                  label = "Use server side Processing in updateSelectizeInput()",
                  value = TRUE),
    selectizeInput(inputId = "in1",
                   label = "Input 1",
                   choices = 1:3,
                   selected = 1),
    selectizeInput(inputId = "in2",
                   label = "Input 2",
                   choices = letters,
                   selected = NULL)
  )

)

# Define server logic required to draw a histogram
server <- function(input, output, session) {

  # show server checkbox value
  observeEvent(input$use_server, ignoreInit = FALSE, ignoreNULL = FALSE, {
    print(paste("server:", input$use_server))
    })

  # show input 1 value
  observeEvent(input$in1, ignoreInit = FALSE, ignoreNULL = FALSE, {
    print(paste("Input 1:", input$in1))
    })

  # show input 2 value
  observeEvent(input$in2, ignoreInit = FALSE, ignoreNULL = FALSE, {
    print(paste("Input 2:", input$in2))
    })

  # trigger update if input 2
  observeEvent(input$in1, ignoreInit = TRUE, ignoreNULL = FALSE, {
    choices <- sample(LETTERS, size = 5)
    updateSelectizeInput(session = session,
                         inputId = "in2",
                         choices = choices,
                         selected = choices[1],
                         server = input$use_server)
  })
}

# Run the application
shinyApp(ui = ui, server = server)

Logs

> runApp('app')

Listening on http://127.0.0.1:5554
[1] "server: TRUE"
[1] "Input 1: 1"
[1] "Input 2: a"
[1] "Input 1: 2"
[1] "Input 2: "
[1] "Input 2: O"
[1] "Input 1: 1"
[1] "Input 2: "
[1] "Input 2: R"
[1] "server: FALSE"
[1] "Input 1: 2"
[1] "Input 2: N"
[1] "Input 1: 3"
[1] "Input 2: C"
[1] "server: TRUE"
[1] "Input 1: 1"
[1] "Input 2: "
[1] "Input 2: S"
[1] "Input 1: 2"
[1] "Input 2: "
[1] "Input 2: G"

System details

Output of sessionInfo():

R version 4.1.0 (2021-05-18)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Mojave 10.14.6

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shiny_1.6.0

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.6        withr_2.4.2       digest_0.6.27     later_1.2.0       mime_0.10         R6_2.5.0          jsonlite_1.7.2   
 [8] lifecycle_1.0.0   xtable_1.8-4      magrittr_2.0.1    cachem_1.0.5      rlang_0.4.11      promises_1.2.0.1  jquerylib_0.1.4  
[15] bslib_0.2.5.1     ellipsis_0.3.2    tools_4.1.0       tinytex_0.31      httpuv_1.6.1      xfun_0.23         fastmap_1.1.0    
[22] compiler_4.1.0    htmltools_0.5.1.1 sass_0.4.0     
Teebusch commented 1 year ago

Hi @mnist91, I did some digging and I believe this is caused by an unnecessary reset deep within Shiny's javascript code where selectizejs is called. A direct fix would be a lot of work, but you can work around the symptoms by manually "debouncing" the selectizeInput's return value whenever you are using the update function:

library(shiny)

ui <- fluidPage(
  mainPanel(
    checkboxInput("use_server", "use server-side selectizeInput()", TRUE),
    checkboxInput("use_debounce", "debounce server-side selectizeInput()", FALSE),
    selectizeInput("inp", "Input", choices = letters, selected = 1),
    verbatimTextOutput ("log"),
    actionButton("btn", "update selectize"),
  )
)

server <- function(input, output, session) {

  ignore_values <- reactiveVal(FALSE)

  # trigger selectize update on button click
  observeEvent(input$btn, {
    if (input$use_server && input$use_debounce) ignore_values(TRUE)

    updateSelectizeInput(inputId = "inp",
                         choices = letters,
                         selected = sample(letters, 1),
                         server = input$use_server)
  })

  # print previous values to UI
  previous_values <- reactiveVal(c())

  observeEvent(input$inp, {
    if (ignore_values()) {
      ignore_values(FALSE) # ignore update only once
    } else {
      previous_values <- previous_values()
      len  <- min(15, length(previous_values))
      previous_values( c(input$inp, previous_values[0:len]) )
    }
  })
  output$log <- renderPrint({ previous_values() })
}

shinyApp(ui = ui, server = server)

Unfortunately, this will still cause the Input Element to "flicker" when using the update function. I hope this gets fixed on Shiny's end eventually. There seem to be quite a few issues with selectizeInput().