skip to Main Content

I am coding a Shiny App, which generates some plots upon pressing a button. The calculation starts when the user presses the butten, and I want to update the label of the button once it is pressed to show that the calculation is currently happening. After the calculation is finished, it should revert back to the "default" state. I thought this would be easy to do with updateActionButton, but somehow I cant get it to work. Here is a small reproducible example.

if (interactive()) {
  
  ui <- fluidPage(
    actionButton("update", "Do calculation"),
  )
  
  server <- function(input, output, session) {
    observeEvent(input$update, {
      # Change the button label
      updateActionButton(session, "update", label = "Running calculation")
      
      # This is a random calculation which takes a few seconds as a placeholder.
      result <- 0
      for (i in 1:5000000) {
        result <- result + sqrt(i)
      }
      
      updateActionButton(session, "update", label = "finished calculation") 
    })
  }
  
  shinyApp(ui, server)
}

It seems like it simply skips the first updateActionButton call…
Any insights into what I am doing wrong are much appreciated!

Cheers!

2

Answers


  1. Chosen as BEST ANSWER

    With the help of Mwavu, I managed to accomplish what I was trying. Although to be honest, I dont really understand why it works and would greatly appreciate if somebody could explain to me why it works.

    library(shiny)
    
    ui <- fluidPage(
      loadingButton("renderButton", "Render Plot"),
      plotOutput("myPlot"),
      textOutput("renderStatus") #if i remove this, the code doesnt work
    )
    
    server <- function(input, output, session) {
      rendered <- reactiveVal(FALSE)
      
      observeEvent(input$renderButton, {
        rendered(FALSE)  # Reset the rendered status
        output$myPlot <- renderPlot({
          # Your plotting code here
          plot(runif(100000), runif(100000), main = "New Plot") # random plot which takes  a few seconds to render
          rendered(TRUE)  # Mark the plot as rendered
        })
      })
      
      output$renderStatus <- reactive({ # <- if I remove the output$renderStatus the code doesnt work anymore (it is stuck in the "loading" annimation)
        if (rendered()) {
          resetLoadingButton(inputId = "renderButton")
        } 
      })
    }
    
    shinyApp(ui, server)
    

    Specifically, I do not understand why it is necessary to include the textOutput("renderStatus") and the output$renderStatus. I thought it should be enough to have this code block

    reactive({ 
        if (rendered()) {
          resetLoadingButton(inputId = "renderButton")
        } 
      })
    

    to trigger the default state of the button.


  2. What is happening?

    From ?updateActionButton:

    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.

    This means that when you click on the button, the updates will only be sent once the for loop is done.

    Since you’re trying to update the label of the button twice, only the last update is sent to the client.

    There are several solutions, I’m going to highlight the 2 easiest ones:

    Solution 1

    Set the onclick attribute of the button.

    This way, you avoid requiring communication from the server for the UI to update. All the server needs to do is alert the client once it is done with the calculations.

    Solution 1 Demo

    library(shiny)  
    ui <- fluidPage(
      actionButton(
        "update",
        "Do calculation",
        onclick = "$('#update').html('running...')"
      ),
    )
    
    server <- function(input, output, session) {
      observeEvent(input$update, {
        # This is a random calculation which takes a few seconds as a placeholder.
        result <- 0
        for (i in 1:5000000) {
          result <- result + sqrt(i)
        }
        
        updateActionButton(session, "update", label = "finished calculation")
      })
    }
    
    shinyApp(ui, server)
    

    Solution 2

    Use {shinyFeedback}.

    This way, you’ll avoid writing ad-hoc inline js like in Solution 1 above. You’ll also get a nice spinner on the button.

    Solution 2 demo

    library(shiny)
    library(shinyFeedback)
    ui <- fluidPage(
      useShinyFeedback(),
      loadingButton(
        inputId = "update",
        label = "Do calculation",
        loadingLabel = "running..."
      ),
    )
    
    server <- function(input, output, session) {
      observeEvent(input$update, {
        # This is a random calculation which takes a few seconds as a placeholder.
        result <- 0
        for (i in 1:5000000) {
          result <- result + sqrt(i)
        }
        resetLoadingButton(inputId = "update")
      })
    }
    
    shinyApp(ui, server)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search