Table of content for chapter 13

Chapter section list

13.1 Introdcution

The initial impression of Shiny is often that it’s “magic”. Magic is great when you get started because you can make simple apps very very quickly. But magic in software usually leads to disillusionment: without a solid mental model, it’s extremely difficult to predict how the software will act when you venture beyond the borders of its demos and examples. And when things don’t go the way you expect, debugging is almost impossible. Once you’ve formed an accurate mental model of reactivity, you’ll see that there’s nothing up Shiny’s sleeves: the magic comes from simple concepts combined in consistent ways.

13.2 Why do we need reactive programming?

Reactive programming is a style of programming that focuses on values that change over time, and calculations and actions that depend on those values. Reactivity is important for Shiny apps because they’re interactive: users change input controls (dragging sliders, typing in textboxes, checking checkboxes, …) which causes logic to run on the server (reading CSVs, subsetting data, fitting models, …) ultimately resulting in outputs updating (plots redrawing, tables updating, …). This is quite different from most R code, which typically deals with fairly static data.

For Shiny apps to be maximally useful, we need reactive expressions and outputs to update if and only if their inputs change. We want outputs to stay in sync with inputs, while ensuring that we never do more work than necessary. To see why reactivity is so helpful here, we’ll take a stab at solving a simple problem without reactivity.

13.2.1 Why can’t you use variables?

In one sense, you already know how to handle “values that change over time”: they’re called “variables”. Variables in R represent values and they can change over time, but they’re not designed to help you when they change. Take this simple example of converting a temperature from Celsius to Fahrenheit:

Listing / Output 13.1: Converting Celsius to Fahrenheit using variables
Code
temp_c <- 10
temp_f <- (temp_c * 9 / 5) + 32
temp_f
#> [1] 50
Code
temp_c <- 50
temp_f
#> [1] 50

The temp_c variable has at the start the value 10, the temp_f variable has the value 50. Later we have changed the value of temp_c to 50.

Now there are two big differences using variable contra the concept of reactivity:

  1. Changing temp_c does not affect temp_f!
  2. Variables can change over time, but they never change automatically.

13.2.2 What about functions?

You could instead attack this problem with a function:

Listing / Output 13.2: Converting Celsius to Fahrenheit using a function
Code
temp_c <- 10
temp_f <- function() {
  message("Converting") 
  (temp_c * 9 / 5) + 32
}
temp_f()
#> Converting
#> [1] 50
Code
temp_c <- -3
temp_f() 
#> Converting
#> [1] 26.6
Code
temp_f()
#> Converting
#> [1] 26.6

is a slightly weird function because it doesn’t have any arguments, instead it accesses temp_c from its enclosing environment. This is valid R code, using lexical scoping for accessing the variable.)

solves the first problem that reactivity is trying to solve: whenever you access temp_f() you get the latest computation. It doesn’t, however, minimize computation. Every time you call temp_f() it recomputes, even if temp_c hasn’t changed.

Computation is cheap in this trivial example, so needlessly repeating it isn’t a big deal, but it’s still unnecessary: if the inputs haven’t changed, why do we need to recompute the output?

13.2.3 Event-driven programming

Since neither variables nor functions work, we need to create something new. In previous decades, we would’ve jumped directly to event-driven programming. Event-driven programming is an appealingly simple paradigm: you register callback functions that will be executed in response to events.

We could implement a very simple event-driven toolkit using R6, as in the example below. Here we define a DynamicValue that has three important methods: get() and set() to access and change the underlying value, and onUpdate() to register code to run whenever the value is modified. If you’re not familiar with {R6}, don’t worry about the details, and instead focus on following examples.

Listing / Output 13.3: Event-driven programming in R with R6::R6Class()
Code
DynamicValue <- R6::R6Class("DynamicValue", list(
  value = NULL,
  on_update = NULL,

  get = function() self$value,

  set = function(value) {
    self$value <- value
    if (!is.null(self$on_update)) 
      self$on_update(value)
    invisible(self)
  },
  
  onUpdate = function(on_update) {
    self$on_update <- on_update
    invisible(self)
  }
))

###############################
temp_c <- DynamicValue$new()
temp_c$onUpdate(function(value) {
  message("Converting") 
  temp_f <<- (value * 9 / 5) + 32
})

temp_c$set(10)
#> Converting
Code
temp_f
#> [1] 50
Code
temp_c$set(-3)
#> Converting
Code
temp_f
#> [1] 26.6

So if Shiny had been invented five years earlier, it might have looked more like this, where temp_c uses <<- (super-assignment operator, see Advanced R) to update temp_f whenever needed.

Event-driven programming solves the problem of unnecessary computation, but it creates a new problem: you have to carefully track which inputs affect which computations. Before long, you start to trade off correctness (just update everything whenever anything changes) against performance (try to update only the necessary parts, and hope that you didn’t miss any edge cases) because it’s so difficult to do both.

13.2.4 Reactive programming

Reactive programming elegantly solves both problems by combining features of the solutions above. Now we can show you some real Shiny code, using a special Shiny mode, reactiveConsole(TRUE), that makes it possible to experiment with reactivity directly in the console.

Listing / Output 13.4: It’s worth bearing in mind that “reactive programming” is a fairly general term. In this book, whenever we refer to “reactive programming”, we are referring specifically to reactive programming as implemented in Shiny.
Code
#|label: reactive-console

glue::glue("(A) ###### special Shiny mode with `reactiveConsole(TRUE)`")
#> (A) ###### special Shiny mode with `reactiveConsole(TRUE)`
Code
glue::glue("No output\n\n")
#> No output
Code
library(shiny)
reactiveConsole(TRUE)

glue::glue("\n(B) ###### create a reactive value with `reactiveValue()`")
#> (B) ###### create a reactive value with `reactiveValue()`
Code
temp_c <- reactiveVal(10) # create
temp_c()                  # get
#> [1] 10
Code
temp_c(20)                # set
temp_c()                  # get
#> [1] 20
Code
glue::glue("\n\n(C) ###### create a reactive expression that depends on `temp_c()`")
#> 
#> (C) ###### create a reactive expression that depends on `temp_c()`
Code
temp_f <- reactive({
  message("Converting") 
  (temp_c() * 9 / 5) + 32
})
temp_f()
#> Converting
#> [1] 68
Code
glue::glue("\n\n(D) ###### if `temp_c` changes, `temp_f` will automatically update")
#> 
#> (D) ###### if `temp_c` changes, `temp_f` will automatically update
Code
temp_c(-3)
temp_c(-10)
temp_f()
#> Converting
#> [1] 14
Code
glue::glue("\n\n(E) ###### if `temp_c()` hasn’t changed, then `temp_f()` doesn’t need to recompute")
#> 
#> (E) ###### if `temp_c()` hasn’t changed, then `temp_f()` doesn’t need to recompute
Code
temp_f()
#> [1] 14
    1. Using a special Shiny mode, reactiveConsole(TRUE) makes it possible to experiment with reactivity directly in the console
    1. As with event-driven programming, we need some way to indicate that we have a special type of variable. In Shiny, we create a reactive value with reactiveVal(). A reactive value has special syntax for getting its value (calling it like a zero-argument function) and setting its value (set its value by calling it like a one-argument function).
    1. Now we can create a reactive expression that depends on reactiveVal().
    1. reactive expressions automatically track all of its dependencies. So that later, if temp_c changes, temp_f will automatically update.
    1. But if temp_c() hasn’t changed, thentemp_f() doesn’t need to recompute, and can just be retrieved from the cache. As the output “Converting” is missing you know that the value is retrieved from cache and not re-computed.

A reactive expression has two important properties:

  1. It’s lazy: it doesn’t do any work until it’s called.
  2. It’s cached: it doesn’t do any work the second and subsequent times it’s called because it caches the previous result.

We’ll come back to these important properties in .

13.3 A brief history of reactive programming

You can see the genesis of reactive programming over 40 years ago in VisiCalc, the first spreadsheet. Spreadsheets are closely related to reactive programming: you declare the relationship between cells using formulas, and when one cell changes, all of its dependencies automatically update. So you’ve probably already done a bunch of reactive programming without knowing it!

13.4 Glossary Entries

term definition
Event-driven programming Event-driven programming is a programming paradigm where the flow of the program is determined by events such as user actions, sensor outputs, or messages from other programs. Programs respond to events with predefined actions, allowing for asynchronous and responsive behavior, often seen in GUI applications and distributed systems. In an event-driven application, there is generally an event loop that listens for events and then triggers a callback function when one of those events is detected.
Reactive expressions Reactive expressions are a key component of reactive programming in Shiny. They are R expressions that use widget input and return a value, updating this value whenever the original widget changes. Reactive expressions cache their values and know when their values have become outdated, which helps in preventing unnecessary re-computation and makes the app faster.
Reactive programming Reactive programming in R Shiny is a coding style that focuses on managing values that change over time and orchestrating actions based on those changes. This feature is crucial for Shiny applications because users interact with dynamic controls like sliders and checkboxes, which trigger server-side processes, such as data retrieval and updates to output elements. In Shiny, reactive programming creates a dynamic link between inputs and outputs, ensuring that outputs refresh automatically when there's a change in inputs.

Session Info

Session Info

Code
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.5.1 (2025-06-13)
#>  os       macOS Sequoia 15.5
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Vienna
#>  date     2025-07-17
#>  pandoc   3.7.0.2 @ /opt/homebrew/bin/ (via rmarkdown)
#>  quarto   1.8.4 @ /usr/local/bin/quarto
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package      * version     date (UTC) lib source
#>  cli            3.6.5       2025-04-23 [1] CRAN (R 4.5.0)
#>  commonmark     2.0.0       2025-07-07 [1] CRAN (R 4.5.0)
#>  crayon         1.5.3       2024-06-20 [1] CRAN (R 4.5.0)
#>  curl           6.4.0       2025-06-22 [1] CRAN (R 4.5.0)
#>  dichromat      2.0-0.1     2022-05-02 [1] CRAN (R 4.5.0)
#>  digest         0.6.37      2024-08-19 [1] CRAN (R 4.5.0)
#>  evaluate       1.0.4       2025-06-18 [1] CRAN (R 4.5.0)
#>  farver         2.1.2       2024-05-13 [1] CRAN (R 4.5.0)
#>  fastmap        1.2.0       2024-05-15 [1] CRAN (R 4.5.0)
#>  glossary     * 1.0.0.9003  2025-06-08 [1] local
#>  glue           1.8.0       2024-09-30 [1] CRAN (R 4.5.0)
#>  htmltools      0.5.8.1     2024-04-04 [1] CRAN (R 4.5.0)
#>  htmlwidgets    1.6.4       2023-12-06 [1] CRAN (R 4.5.0)
#>  httpuv         1.6.16      2025-04-16 [1] CRAN (R 4.5.0)
#>  jsonlite       2.0.0       2025-03-27 [1] CRAN (R 4.5.0)
#>  kableExtra     1.4.0       2024-01-24 [1] CRAN (R 4.5.0)
#>  knitr          1.50        2025-03-16 [1] CRAN (R 4.5.0)
#>  later          1.4.2       2025-04-08 [1] CRAN (R 4.5.0)
#>  lifecycle      1.0.4       2023-11-07 [1] CRAN (R 4.5.0)
#>  litedown       0.7         2025-04-08 [1] CRAN (R 4.5.0)
#>  magrittr       2.0.3       2022-03-30 [1] CRAN (R 4.5.0)
#>  markdown       2.0         2025-03-23 [1] CRAN (R 4.5.0)
#>  mime           0.13        2025-03-17 [1] CRAN (R 4.5.0)
#>  promises       1.3.3       2025-05-29 [1] CRAN (R 4.5.0)
#>  R6             2.6.1       2025-02-15 [1] CRAN (R 4.5.0)
#>  RColorBrewer   1.1-3       2022-04-03 [1] CRAN (R 4.5.0)
#>  Rcpp           1.1.0       2025-07-02 [1] CRAN (R 4.5.0)
#>  rlang          1.1.6       2025-04-11 [1] CRAN (R 4.5.0)
#>  rmarkdown      2.29        2024-11-04 [1] CRAN (R 4.5.0)
#>  rstudioapi     0.17.1      2024-10-22 [1] CRAN (R 4.5.0)
#>  rversions      2.1.2       2022-08-31 [1] CRAN (R 4.5.0)
#>  scales         1.4.0       2025-04-24 [1] CRAN (R 4.5.0)
#>  sessioninfo    1.2.3       2025-02-05 [1] CRAN (R 4.5.0)
#>  shiny        * 1.11.1.9000 2025-07-08 [1] Github (rstudio/shiny@f752856)
#>  stringi        1.8.7       2025-03-27 [1] CRAN (R 4.5.0)
#>  stringr        1.5.1       2023-11-14 [1] CRAN (R 4.5.0)
#>  svglite        2.2.1       2025-05-12 [1] CRAN (R 4.5.0)
#>  systemfonts    1.2.3       2025-04-30 [1] CRAN (R 4.5.0)
#>  textshaping    1.0.1       2025-05-01 [1] CRAN (R 4.5.0)
#>  vctrs          0.6.5       2023-12-01 [1] CRAN (R 4.5.0)
#>  viridisLite    0.4.2       2023-05-02 [1] CRAN (R 4.5.0)
#>  xfun           0.52        2025-04-02 [1] CRAN (R 4.5.0)
#>  xml2           1.3.8       2025-03-14 [1] CRAN (R 4.5.0)
#>  xtable         1.8-4       2019-04-21 [1] CRAN (R 4.5.0)
#>  yaml           2.3.10      2024-07-26 [1] CRAN (R 4.5.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.5-arm64/library
#>  [2] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
#>  * ── Packages attached to the search path.
#> 
#> ──────────────────────────────────────────────────────────────────────────────