Code
temp_c <- 10
temp_f <- (temp_c * 9 / 5) + 32
temp_f
#> [1] 50
Code
temp_c <- 50
temp_f
#> [1] 50
Chapter section list
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.
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.
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:
temp_c <- 10
temp_f <- (temp_c * 9 / 5) + 32
temp_f
#> [1] 50
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:
temp_c
does not affect temp_f
!You could instead attack this problem with a function:
temp_c <- 10
temp_f <- function() {
message("Converting")
(temp_c * 9 / 5) + 32
}
temp_f()
#> Converting
#> [1] 50
temp_c <- -3
temp_f()
#> Converting
#> [1] 26.6
temp_f()
#> Converting
#> [1] 26.6
Listing / Output 13.2 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.)
Listing / Output 13.2 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?
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.
R6::R6Class()
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
temp_f
#> [1] 50
temp_c$set(-3)
#> Converting
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.
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.
#|label: reactive-console
glue::glue("(A) ###### special Shiny mode with `reactiveConsole(TRUE)`")
#> (A) ###### special Shiny mode with `reactiveConsole(TRUE)`
glue::glue("No output\n\n")
#> No output
library(shiny)
reactiveConsole(TRUE)
glue::glue("\n(B) ###### create a reactive value with `reactiveValue()`")
#> (B) ###### create a reactive value with `reactiveValue()`
temp_c <- reactiveVal(10) # create
temp_c() # get
#> [1] 10
temp_c(20) # set
temp_c() # get
#> [1] 20
glue::glue("\n\n(C) ###### create a reactive expression that depends on `temp_c()`")
#>
#> (C) ###### create a reactive expression that depends on `temp_c()`
#> Converting
#> [1] 68
glue::glue("\n\n(D) ###### if `temp_c` changes, `temp_f` will automatically update")
#>
#> (D) ###### if `temp_c` changes, `temp_f` will automatically update
temp_c(-3)
temp_c(-10)
temp_f()
#> Converting
#> [1] 14
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
temp_f()
#> [1] 14
reactiveConsole(TRUE)
makes it possible to experiment with reactivity directly in the consolereactiveVal()
. 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).reactiveVal()
.temp_c
changes, temp_f
will automatically update.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:
We’ll come back to these important properties in Chapter 14.
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!
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
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.
#>
#> ──────────────────────────────────────────────────────────────────────────────