in Rshiny, why does the javascript function setInputValue make observeEvent run twice?

28 Views Asked by At

I'm making an Rmarkdown html_document shiny app which has a selectInput.

I have made a minimal code example to reproduce the issue I have:

---
output: html_document
runtime: shiny
---

```{r echo=FALSE}
selectInput("selectYear", label = "Year", choices = c("All", 2006:2021), selected = "All")

observeEvent(input$selectYear, {
  message("input$selectYear = ", input$selectYear) 
  throttle(
    {
      updateSelectInput(session, "selectYear", choices = c("All", 2006:2021), selected = input$selectYear)
    },
    100000
  )
})
```<!-- -->

(you can remove the <!-- -->, that's only for code formatting here in SO. EDIT: To run this, I store it in a Rmd file and execute with rmarkdown::run("filename.Rmd", shiny_args = list(port = 5050, host = "0.0.0.0")))

This minimal app looks like this:
screenshot showing a dropdown select with All selected

I have also build several charts (striped down in this minimal example) and wanted to trigger a change of selectYear with JavaScript (for example on the event of clicking a bar in chart). Here is a snippet to reproduce the issue:

Shiny.setInputValue('selectYear', 2015);

The issue I have is that this javascript triggers twice the observeEvent, we can see in the log:

input$selectYear = 2015
input$selectYear = 2015

I know it is not an issue in this example because it's too fast too notice, but when you have more code with data processing and many things happening in the app, this becomes noticeable and my chart flickers twice with whole seconds apart which is unacceptable.

Note that this double trigger doesnt happen if you use the html dropdown.

The reason to have the observeEvent is that if I don't have it, then dropdown doesn't update visually with the new value of 2015, the value only changes in the background but the dom doesnt change. Therefore I have setup the observeEvent with updateSelectInput to change the display of the dropdown as well. I don't see any other solution for that, but if you have that could also solve my issue here.


TRIED AND FAILED:

  • debounce / throttle (as you can see in the snippet above)

  • sending a flag from JS to prevent double trigger:

    Shiny.setInputValue('updateFromJS', true);

checkboxInput("updateFromJS", "updateFromJS", TRUE)
observeEvent(input$selectYear, {
  message("input$selectYear = ", input$selectYear, " input$updateFromJS = ", input$updateFromJS)
  if (input$updateFromJS == TRUE) {
    message("updating...")
    updateCheckboxInput(session, "updateFromJS", "updateFromJS", FALSE) ## i.e. input$updateFromJS <- FALSE
    updateSelectInput(session, "selectYear", choices = c("All", 2006:2021), selected = input$selectYear)
  }
})

I also wonder why does it run twice and not in an infinite loop. And how come throttle and debounce do not work?

1

There are 1 best solutions below

0
Stéphane Laurent On BEST ANSWER

When you run Shiny.setInputValue("selectYear", 2015), then input$selectYear takes the value 2015, a number. Then the updateSelectInput sets input$selectYear to the value "2015", a character string. There's no infinite loop because nothing changes input$selectYear after that.

To see that:

  observeEvent(input$selectYear, {
    message("input$selectYear = ", mode(input$selectYear)) 
    updateSelectInput(session, "selectYear", selected = input$selectYear)
  })