3

I'm new to Shiny and have hit a problem I can't find an answer for. Basically, I have a Shiny app that does some long calculations in a loop and I want it to output a "progress report" every few iterations. However, even though I reassign my reactive variable within the loop, the output doesn't update until the loop (or entire function?) has finished.

Here is a simplified test case of what I mean:

library(shiny)

# Basic interface
ui <- fluidPage(
     actionButton("run", "Run"),
     textOutput("textbox")
)

# Basic server with loop
server <- function(input, output) {

  textvals=reactiveValues(a=0)

  observeEvent(input$run, {
    for(i in 1:10){
      textvals$a=i   # Expect output to update here, but doesn't
      Sys.sleep(0.1) # Slight pause so isn't instantaneous
    }
  })

   output$textbox <- renderText({
      textvals$a
   })
}

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

What I would expect is that the display would update 1, 2, 3, ... 10 as the loop executes. Instead, it just jumps straight from 0 to 10. How can I force an update partway through the loop?

Thank you.

Jason
  • 33
  • 3
  • Does it help: https://shiny.rstudio.com/articles/progress.html ? – Chelmy88 Sep 24 '19 at 12:55
  • Possible duplicate of [How can I make UI respond to reactive values in for loop?](https://stackoverflow.com/questions/56267073/how-can-i-make-ui-respond-to-reactive-values-in-for-loop) – ismirsehregal Sep 24 '19 at 12:57

1 Answers1

0

With using invalidateLater you can get something closed to what you want. Not the shortest way to do it I think, but it may help you to find a better solution.

library(shiny)

# Basic interface
ui <- fluidPage(
  actionButton("run", "Run"),
  textOutput("textbox")
)

# Basic server with loop
server <- function(input, output, session) {

  textvals <- reactiveVal(0)
  active <- reactiveVal(FALSE)

  output$textbox <- renderText({
   textvals()
  })

  observe({
    invalidateLater(1000, session)
    isolate({
      if (active()) {
        textvals(textvals() + 1)
        if (textvals() > 9) {
          active(FALSE)
        }
      }
    })
  })

  observeEvent(input$run, {
    active(TRUE)
  })
}

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

By the way, reactive and for loops don't really get on well. This may help : https://gist.github.com/bborgesr/e1ce7305f914f9ca762c69509dda632e

gdevaux
  • 2,308
  • 2
  • 10
  • 19
  • Thanks, I'll look into this option. I ran across the github page, but its explanations are pretty opaque (at least for a newbie like me). – Jason Sep 24 '19 at 15:23
  • FYI, I made this change and it worked. Can someone explain the logic of this to me? Reading the docs, it seems that invalidateLater() causes the observe() function it's inside to be invalidated x miliseconds after it's run, so it runs again. But I don't understand why the isolate() function is necessary, and why setting active to FALSE stops the iterations when invalidateLater() is still called. Can someone explain the programming logic of what's going on? – Jason Sep 25 '19 at 12:17