A note on Shiny reactivity

Posted on September 24, 2022 by Stéphane Laurent
Tags: R, shiny

In the ‘shiny’ package, the reactiveConsole function allows to enable reactivity at the console, for the purposes of experimentation and learning (that doesn’t work in a R Markdown document).

library(shiny)
reactiveConsole(TRUE)

So let’s play with the Shiny reactivity, without a Shiny app.

The code below creates an observer which observes a reactive value:

x <- reactiveVal(NULL)
observeEvent(x(), {
  print("An event has occured.")
})

Recall that x is a function; when calling it with an argument, this sets the value, and when calling it without argument, this read the value.

Let’s try it:

> x(2)  # observer triggered
[1] "An event has occured."
[1] "The value of x is 2"
> x(2)  # observer not triggered, because same value
> x(-2) # observer triggered
[1] "An event has occured."
[1] "The value of x is -2"

Nothing surprising. Everybody knows that.

Now, let’s define a reactive conductor which calculates the square of our reactive value, and let’s observe its value:

x <- reactiveVal(NULL)
xsquared <- reactive({
  x()^2
})
observeEvent(xsquared(), {
  print("An event has occured.")
  print(paste("The value of x² is", xsquared()))
})

What happens if we execute the same code as before? The same output as before? The answer is no:

> x(2)  # observer triggered
[1] "An event has occured."
[1] "The value of x² is 4"
> x(2)  # observer not triggered, because nothing has changed
> x(-2) # observer triggered, while x² has not changed!
[1] "An event has occured."
[1] "The value of x² is 4"

This is an important difference between a reactive value and a reactive conductor. The reactive conductor is also an observer, and here it observes the reactive value x(). Then it reacts when x() changes and even though its output does not change, it triggers an event.

Note that an observer observing x()^2 is also triggered when x() takes the value 2 then -2:

x <- reactiveVal(NULL)
observeEvent(x()^2, {
  print("An event has occured.")
}, ignoreInit = TRUE)

> x(2)
[1] "An event has occured."
> x(-2)
[1] "An event has occured."