4

I am building an R Shiny UI (split as UI and Server) which spends about three hours building a data.frame of disease clinical records given some Shiny UI parameters. On completion, the data.frame is passed to a Cox model and the result will be displayed as a plot.

When running R on the terminal the code will print information during those three hours e.g., how many patients/drugs it has parsed.

I have tried using separate textOutput UI features but I can't seem to get the textOutput to update from within a function call executed on the event of clicking a button. I believe this could be scope related. My code is split by UI and Server:

Note: the button is clicked once and I would like to see the textOutput to be updated several times on that click given a call from within a loop.

library(shiny)


shinyUI(fluidPage(

  # Application title
  titlePanel("CPRD EHR Statistical Toolset"),


  sidebarLayout(
    sidebarPanel(
      helpText("A long list of Input features below here."),
      mainPanel(
        h4("Medical record construction"),
        textOutput("numPatientsOutput"),
        actionButton("executeButton", "Execute Cox")
      )
    )
  )
))

library(shiny)

shinyServer(function(input, output) {

  observeEvent(input$executeButton, {
    coxDF<-runBuildModel(input, output)
  }) #endf of execute action

})

runBuildModel <- function(input, output) {
  for(i in 1:10) {
    #This is not updating.
    output$numPatientsOutput <- renderText({paste("patient:",i)})
  }
}

Anthony Nash
  • 834
  • 1
  • 9
  • 26

2 Answers2

4

I realize that this is resurrecting and VERY old question, but I've faced the same problem myself and found a way to solve it. I wanted to provide an answer here for others that might find this page.

I found a workaround for this using the shinyjs package, code below. The take home message is that by using shinjs::html(), the effect on the htmlOutput is immediate. I even added a fancy fade out at the end to hide the message.

It does create yet another package dependency, but it solves the problem. I'm sure there is a way that one could write a small JavaScript function and add it to the Shiny application to accomplish this same result. Unfortunately, I don't know JavaScript. (References for including JS code in a Shiny app - JavaScript Events in Shiny, Add JavaScript and CSS in Shiny)

library(shiny)
library(shinyjs)

ui <- shinyUI(fluidPage(
  
  # Application title
  titlePanel("CPRD EHR Statistical Toolset"),
  
  sidebarLayout(
    sidebarPanel(
      helpText("A long list of Input features below here.")),
    mainPanel(
      useShinyjs(),
      h4("Medical record construction"),
      htmlOutput("numPatientsOutput"),
      actionButton("executeButton", "Execute Cox")
    )
  )
))

server <- shinyServer(function(input, output) {
  runBuildModel <- function(input, output) {
    for(i in 1:10) {
      #This is not updating.
      Sys.sleep(1)
      rv$outputText = paste0(rv$outputText,"patient:",i,'<br>')
      shinyjs::html(id = 'numPatientsOutput', rv$outputText)
    }
  }
  
  rv <- reactiveValues(outputText = '')
  
  observeEvent(input$executeButton, {
    coxDF<-runBuildModel(input, output)
  }) #endf of execute action
})
KirkD-CO
  • 1,603
  • 1
  • 22
  • 35
3

The server basically runs all the code before rendering it. That's why you only get your last line of text.

What you can do is create a reactiveValue and update this value in the for loop. In addition, you have to create an observer to keep track of the value.

Working example

library(shiny)

ui <- shinyUI(fluidPage(

  # Application title
  titlePanel("CPRD EHR Statistical Toolset"),


  sidebarLayout(
    sidebarPanel(
      helpText("A long list of Input features below here.")),
    mainPanel(
      h4("Medical record construction"),
      htmlOutput("numPatientsOutput"),
      actionButton("executeButton", "Execute Cox")
    )

  )
))

server <- shinyServer(function(input, output) {
  runBuildModel <- function(input, output) {
    for(i in 1:10) {
      #This is not updating.
      rv$outputText = paste0(rv$outputText,"patient:",i,'<br>')
    }
  }

  rv <- reactiveValues(outputText = '')

  observeEvent(input$executeButton, {
    coxDF<-runBuildModel(input, output)
  }) #endf of execute action

  observe(output$numPatientsOutput <- renderText(HTML(rv$outputText)))
})



shinyApp(ui, server)
Wilmar van Ommeren
  • 7,469
  • 6
  • 34
  • 65
  • This example is not working. I have run it, adding Sys.sleep(1) at the for loop, and the output is updated only **after** the for loop finishes running. – Sam Aug 01 '21 at 17:17
  • Confirmed this example does NOT work. – Ian Dec 29 '21 at 03:28