A few things I learned about shiny (and reactive programming)

[This article was first published on shinyData, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

One of the things I really like about shiny is that it has excellent documentation: the tutorial, articles and gallery go a long way in helping newcomers as well as intermediate programmers mastering the structure and features of shiny. Still, there are a few things I found lacking from the documentation but important to understand especially if your shiny app is going to be more than just a few lines of R code. I’m sharing them here hoping they can help others avoid some of the roundabouts I took as I learn my way in shiny.

Reactive dependencies are dynamic

What does this mean? It’s probably best illustrated with a somewhat contrived example. Consider the following two observers:

observe({
  if(input$a=="good"){
    print("good")
  } else {    
    print(input$b)
  }
})

observe({
  a <- input$a
  b <- input$b
  if(a=="good"){
    print("good")
  } else {
    print(b)
  }
})

Are they equivalent since the second is simply a reorganization of the first? The answer is no. The subtle difference between the two is that the second observer will always have dependencies on both input$a and input$b (so will re-run whenever one of them changes), vs the second observer will only depend on input$b when input$a != "good" (so when input$a == "good", it will only print “good” once no matter how many times you change input$b). The reason for this behavior is that the dependencies are built as the code is run (ie, dynamically), not based on how the observer (or any reactives) is initially defined. This may seem like a bug to some people, but it’s actually consistent with how R works in general, and can be a very useful feature once it’s well understood. I personally used this feature extensively in my R package shinyData to help separate the business logic from the UI.

Note: see this discussion post for a user’s question on this topic.

Reactives: order of execution

Here’s my understanding of the order of execution for reactives: when a user changes something in the browser, the browser sends the new input value to the server, which triggers a flush event and invalidates all the input’s dependents. Then all the reactive endpoints (like observers and outputs) are refreshed, which may also trigger a refresh of the reactive conductors (or reactive expressions defined with the reactive function). The part is actually well documented. However, things get trickier when you have calls to update inputs (like updateSelectInput). They will not execute until all the currently invalidated reactives finished executing, then the server sends messages back to the browser for all the update-input calls. If this results in any changes in input values, then another cycle of refreshing continues. So if you are not careful, it’s pretty easy to end up with a infinite loop if your update-input calls actually change some input values (not just the choices of a select input, for example).

After a flush event occurred, you can control which reactive endpoints are refreshed first by setting the “priority” argument (see ?observe). In my opinion this technique should only be used when absolutely necessary since the order of execution for reactives are in general unpredictable. Additionally, if a reactive is going to be invalidated as a result of an update-input call, then it is going to be refreshed after all the currently invalidated reactives finished refreshing no matter how high the priority you set it to be.

Use of isolate to prevent accidental dependencies

When you app gets more complex, it’s really important to prevent unintended dependencies for your reactives. I found the following coding pattern very helpful in showing what the dependents of a reactive are:

observe({
  ## part of code this reactive should take dependency on
  ...
  ...
  isolate({
    ## part of code this reactive should NOT take dependency on
    ...
    ...
  })
})

Conditional panel

The online article on dynamic UI explains that the condition of a conditional panel can use the result of a reactive expression in addition to input values. This provides a tremendous level of flexibility. So you can do things like:

# Partial example
## ui.R
conditionalPanel(condition="output.foo", ...)
## server.R
output$foo <- reactive({
  ...
})

However, what’s missing from the documentation is that in order to make it actually work, you need to add the following line to your server.R:

outputOptions(output, "foo", suspendWhenHidden=FALSE)

This is necessary because you’re unlikely to display the value of output$foo in your UI, and shiny by default suspends all output reactives when they are not displayed.

reactiveValues

reactiveValues is a powerful construct in shiny. Most of the common objects in R (like lists and data frames) have copy-on-modify semantics that behave somewhat in between reference classes and value classes as in languages like C or PHP. Basically, what copy-on-modify means is that when a list is assigned to another variable, that list is not copied immediately, but it is copied when some elements are modified with the new variable. This mechanism works well in most usages of R, but it can get quite frustrating if you try any type of object oriented programming with R, as you would normally expect objects like lists to have reference semantics, ie, a copy is never made unless you explicitly choose to do so. Fortunately, this is the case with reactiveValues, as can be seen in the following simple example:

library(shiny)
values <- reactiveValues(a = 1)
isolate(values$a)
## [1] 1
v1 <- values
v1$a <- 2
isolate(values$a)
## [1] 2

What this means is that you don’t have to worry about creating accidental copies of a reactiveValues object you created so the dependent reactives don’t get updated.

Note members of reactiveValues can be any valid R objects (they can even be other reactiveValues). How does shiny determine if a member is changed if the member is a complex object like a list? Below is a simple shiny app I put together for testing this. As you can see for yourself if you run the app, shiny is pretty smart about detecting the change. It looks “deeply” in the object, so a change in the number of elements or an element’s value will both be detected.

## ui.R
shinyUI(fluidPage(
  sidebarLayout(
    sidebarPanel(
      numericInput('aa','a$aa',value=1),
      numericInput('bbb','a$bb$bbb',value=1),
      actionButton('addList', "Add to list a"),
      br(), br(),
      numericInput('rr','r$rr',value=1),
      actionButton('addToR', "Add to r")
      ),
    mainPanel(
      h3('values'),
      verbatimTextOutput('a')
      )
    )
))

## server.R
library(shiny)
shinyServer(function(input, output) {
  values <- reactiveValues(a = list(aa=1, bb=list(bbb=1)), r=reactiveValues(rr=1))

  output$a <- renderPrint({
    list(a=values$a, r=reactiveValuesToList(values$r))
  })
  observe({
    input$aa
    isolate({  ## use isolate to avoid dependency on values$a
      values$a$aa <- input$aa  ## no need to use <<- because of reference semantics with reactiveValues
    })
  })
  observe({
    input$bbb
    isolate({
      values$a$bb$bbb <- input$bbb
    })
  })
  observe({
    input$rr
    isolate({
      values$r$rr <- input$rr
    })
  })
  observe({
    if(input$addList>0){  ## not run when being initialized
      isolate({
        values$a[[paste0('aa', length(values$a))]] <- 1
      })
    }
  })
  observe({
    if(input$addToR>0){  ## not run when being initialized
      isolate({
        values$r[[paste0('rr', length(reactiveValuesToList(values$r)))]] <- 1
      })
    }
  })
})

That’s probably enough for this post. If others find it useful, I might put together a follow-on post.


To leave a comment for the author, please follow the link and comment on their blog: shinyData.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)