Reactive Flow
One familiar way of thinking about the Reactive Flow is in the context of a spreadsheet (e.g. Google Sheets, Microsoft Office Excel). Open Google Sheets or Excel Suppose you write a value into a cell in a spreadsheet. And then in another cell you write a formula that depends on that cell… First the formula is calculated with the value you originally typed. Now when you change the value of the original cell, the result of the formula will automatically update, or react, to this change. In a shiny app, reactivity happens in a similar fashion. In our (basic) app that we built in the last session, we have a selectInput with the inputID “z.” No matter what the user inputs, the inputID always stores it as “z,” so whenever the user selects a different option from the list, the value of “z” is updated in the input (input$) list. Reactivity automatically occurs when an input value is used to render an output object. Here is a roadmap of the reactive flow in shiny, though just for now, we’ll just focus on the straight path between an input and output and discuss the other features later in this course. The user selects an input, the input goes through some expression in the server, and an output is rendered. Each time a user changes their input selection, the expression that generates the output will automatically re-execute, and the relevant output will be re-rendered based on the new value of the input.
In a shiny application, there’s no need to explicitly define relationships between inputs and outputs and tell R what to do when each input changes. Shiny automatically handles these details for you.
The Theory of Reactivity
Reactivity makes shiny apps more efficient but also more complex. There are three kinds of objects in reactive programming: reactive sources, conductors, and endpoints.
Reactive sources are typically user inputs that come through a browser interface (input$*
).
Reactive endpoints are objects that appear in a user’s browser window, such as a plot or table of values (output$*
). One reactive source can be connected to multiple endpoints and vice versa.
Reactive conductors (reactive()
) are reactive components between sources and endpoints. A conductor can be both a child and parent (both being a dependent and having dependents) whereas a source can only be a parent (can only have dependents) and an endpoint can only be a child (can only be a dependent).
We’ve already made use of these reactive expressions to build our app, updating the backend dataframe, visualized dataframe, map, ggplot, and download every time we select a new date range.
Question for the Audience!
The nasa_fireball_reactive()
expression in our app is a…
Reactive source
Reactive conductor
Reactive endpoint
By using a reactive expression for the subsetted data frame, we were able to get away with subsetting the data only once, but using it 4 times. Reactive conductors, in general, are very nice, because:
You don’t have to repeat yourself and have copy-and-paste code, and
You can decompose large, complex calculations into smaller pieces to make them more understandable.
These advantages of reactive conductors are similar to those gained by decomposing a large, complex R script into a series of smaller functions that build on each other.
Functions vs. Reactives
Everytime you call a function, R will evaluate it.
Reactive expressions are lazy, and will only execute when their input changes. Even if you call a reactive expression multiple times, it only re-executes when its inputs change.
Reactlog
Using many reactive expressions in your app can create a complicated dependency structure in your app. The reactlog is a graphical representation of this dependency structure, and it also gives you very detailed information about what’s going on under the hood as Shiny goes through and evaluates your application.
Best Practice: View your reactlog.
In a fresh R session, type:
options(shiny.reactlog = TRUE)
Then launch your app as you normally would, and press CTRL + F3 in the app to view your reactlog. More information about reactlogs can be found in the RStudio Documentation.
Important Advice from RStudio: “For security and performance reasons, do not enable shiny.reactlog
in production environments. When the option is enabled, it’s possible for any user of your app to see at least some of the source code of your reactive expressions and observers.”
Reactors vs. Observers
Implementation of reactive conductors (e.g. reactive()
):
Reactive conductors can access reactive values or other reactive expressions, and they return a value. This is useful for caching the results of any procedure that happens in response to user input.
Implementation of reactive endpoints (e.g. observe()
, output$*
):
An output$*
object is an observer. What’s going on under the hood is that a render function returns a reactive expression, and when you assign it to an output$*
value, Shiny automatically creates an observer that uses the reactive expression. Observers can access reactive sources and reactive expressions, but they don’t return a value. They are primarily used for their side effects, including sending data to a web browser.
Synopsis of Reactors vs. Observers
Similarities: Both store expressions that can be executed.
Differences:
Reactive expressions return values, observers don’t.
Observers (and endpoints in general) respond to changes in their dependencies, but reactive expressions (and conductors in general) do not.
Reactive expressions must not have side effects, while observers are only useful for their side effects.
Most importantly:
reactive()
is for calculating values, without side effects.
observe()
is for performing actions, with side effects.
Do not use observe()
when calculating a value, and especially don’t use reactive()
for performing actions with side effects.
Question for the Audience!
Which of the following does not have a side effect?
var <- 125
source("script.R")
library(ggplot2)
2/3
Question for the Audience!
Which of the following functions has a side effect?
function(a, b) { log(a/b) }
function(data) { hist(data, plot = TRUE) }
function() { read.tsv("~/data/raw.txt") }
function(x) { summary(x) }
3 Main Ideas of Reactivity
Reactives are equivalent to no-argument functions. Think about them as simultaneously functions and variables that can depend on user input and other reactives.
Reactives are for reactive values and expressions; observers are for their side effects.
Do not define a reactive()
inside a render*()
function.