shinyshinyjs

shinyjs is not excecuting all actions inside observeEvent


I got an app that when you click on an actionButton, it should set an input to a certain value; and a click action should be made through shinyjs. However, only the first thing is happening; and I don't know how to make the second one happen. Or maybe it IS happening; but the data inside the reactive object is not getting updated.

Heres a minimal reprex:

library(shiny)

ui <- fluidPage(
  shinyjs::useShinyjs(),
  
  selectInput("selector", label = "Carb selector", choices = unique(mtcars$carb)),
  actionButton("generate", "OK!"),
  tableOutput("results"),
  actionButton("reset", "RESET"),
  
)

server <- function(input, output, session) {
  
  data <- reactive({
    req(input$generate)
    isolate(
    mtcars %>% filter(carb == input$selector)
    )
  })
  
  output$results <- renderTable(data())

  observeEvent(input$reset, {
    
    updateSelectInput(session, 
                      inputId = "selector",
                      label = "Carb selector updated",
                      choices = unique(mtcars$carb),
                      selected = 1)
    
    shinyjs::click("generate")#This does not seem trigger when you hit reset!
    
  })
    
}

shinyApp(ui, server)

Solution

  • Why this behavior?

    The architecture of your code is a bit questionable, but I will point out why it is behaving this way.

    To understand this, let's first break the observer on input$reset into two:

      observeEvent(input$reset, {
        updateSelectInput(
          session,
          inputId = "selector",
          label = "Carb selector updated",
          choices = unique(mtcars$carb),
          selected = 1
        )
      })
    
      observeEvent(input$reset, {
        shinyjs::click("generate") # This does not seem trigger when you hit reset!
      })
    

    After that, let's have a look at the documentation of updateSelectInput:

    The input updater functions send a message to the client, telling it to change the settings of an input object. The messages are collected and sent after all the observers (including outputs) have finished running.

    And there lies the answer. In other words, shinyjs::click("generate") will always execute before update*Input() effects kick in.

    Basically, this is the flow:

    1. User clicks 'reset' btn
    2. That leads to a click on 'generate' btn
    3. The `data()` reactive is re-evaluated
    4. All observers are re-evaluated
    5. `update*Input()` sends updates to the client
    

    Suggested solution

    We can use number 4 above to our advantage by having a reactiveVal() that always has the most current value of 'selector' and is a step ahead of update*Input().

    I have made a few changes to your code to make it more explicit and straight to the point.

    Demo

    library(shiny)
    library(dplyr)
    
    ui <- fluidPage(
      shinyjs::useShinyjs(),
      selectInput("selector", label = "Carb selector", choices = unique(mtcars$carb)),
      actionButton("generate", "OK!"),
      tableOutput("results"),
      actionButton("reset", "RESET"),
    )
    
    server <- function(input, output, session) {
      rv_selector <- reactiveVal()
      observeEvent(input$selector, rv_selector(input$selector))
    
      data <- reactive({
        mtcars %>% filter(carb == rv_selector())
      }) |> bindEvent(input$generate)
    
      output$results <- renderTable(data())
    
      observeEvent(input$reset, {
        updateSelectInput(
          session,
          inputId = "selector",
          label = "Carb selector updated",
          choices = unique(mtcars$carb),
          selected = 1
        )
        rv_selector(1) # Ensures server is updated before the client
        shinyjs::click("generate")
      })
    }
    
    shinyApp(ui, server)