Skip to content

Instantly share code, notes, and snippets.

@bborgesr
Created May 5, 2017 17:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bborgesr/61409e3852feb991336757f06392e52a to your computer and use it in GitHub Desktop.
Save bborgesr/61409e3852feb991336757f06392e52a to your computer and use it in GitHub Desktop.
Two simple examples of iterative printing in Shiny
# By default Shiny will always wait for any reactive dependencies to finish (re)calculcating
# before displaying the output that depends on them. Without async reactivity in Shiny (which
# is not currently available, but it's in the works), it's actually not really possible to
# make a reactive dependency be available before it finishes computing. But you can redefine
# your reactive so that its output is broken though.
#
# You can achieve this using reactive values, an observer and invalidateLater(). If this is
# new for you, I'd recommend, you read the documentation about invalidateLater().
#
# I'll show two simple examples here. For a more elaborate example, see this gist.
#
# The first and probably best approach is to do something like:
library(shiny)
ui <- fluidPage(
verbatimTextOutput("loc")
)
server <- function(input, output, session) {
rv <- reactiveValues(lines = character(0), n = 0)
observe({
# Re-execute this reactive expression immediately after
# it finishes (though you can also specify a longer time:
# e.g. 1000 means re-execute after 1000 milliseconds have
# passed since it was last executed)
invalidateLater(0, session)
isolate({
if (rv$n >= 100) {
return()
} else {
rv$n <- rv$n + 1
rv$lines <- c(rv$lines, paste("Loop var =", rv$n))
}
})
})
output$loc <- renderPrint({
rv$lines
})
}
shinyApp(ui, server)
# This will re-execute the observer repeatedly until you've reached rv$n = 100 (actually,
# it will keep re-executing forever and it always stops because rv$n will never be
# decremented once it reaches 100). So in the first iteration, rv$lines starts as nothing
# and then becomes the string "Loop var = 1". It returns that and the renderPrint() call
# will print that to the screen. However, immediately after the observer finished, it will
# re-start and the rv$lines object will now have two strings: "Loop var = 1" and
# "Loop var = 2". It returns that and the renderPrint() call will print that to the screen.
# And so on... This happens very fast in the example above, but will be very clear if you
# change the line invalidateLater(0, session) to something like a 1/4 of a second:
# invalidateLater(250, session).
#
# This first approach is IMO intuitive and it stays neatly within the reactive framework
# that Shiny strongly encourages. However, it does have one drawback: the renderPrint()
# call will always print all the strings, each time that rv$lines changes, even though,
# you're just adding a line at the end (so ideally you wouldn't have to re-print everything).
# This isn't an issue for 100 iterations of printing text, but for larger number or
# heavier objects (images, for example), this can become noticeable.
#
# But there is another option that is fairly new to Shiny, which is to use insertUI().
# This option follows imperative, not reactive, logic, so I'd only recommend it if you feel
# like you understand reactivity AND it makes sense for your use case. In particular, this
# approach will let you print things one after the other (instead of always printing one
# big block), but it loses track of them. If these are values that could change later, or
# that you'd like to read in some other place, you shouldn't use this approach. (There are
# ways to use insertUI in more complicated ways, but that's another discussion, because I
# don't think that's what you need in this post.) This has the advantage of being faster
# and not keeping a possible very large set of strings/objects in memory. If this is new
# for you, I'd recommend, you read the documentation about insertUI().
library(shiny)
ui <- fluidPage(
div(id = "placeholder")
)
server <- function(input, output, session) {
rv <- reactiveValues(n = 0)
observe({
invalidateLater(0, session)
isolate({
if (rv$n >= 100) {
return()
} else {
rv$n <- rv$n + 1
insertUI("#placeholder", ui = p("Loop var = ", rv$n))
}
})
})
}
shinyApp(ui, server)
@bborgesr
Copy link
Author

bborgesr commented May 5, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment