Code
x <- reactiveVal(10)
x() # get
x(20) # set
x() # get
r <- reactiveValues(x = 10)
r$x # get
r$x <- 20 # set
r$x # get
#> [1] 10
#> [1] 20
#> [1] 10
#> [1] 20
Chapter section list
There are two types of reactive values:
reactiveVal()
.reactiveValues()
.They have slightly different interfaces for getting and setting values.
R Code 15.1 : Interfaces for getting and setting reactive values
While they look different, they behave the same, so you can choose between them based on which syntax you prefer. In this book Hadley use reactiveValues()
because the syntax is easier to understand at a glance, but in his own code h tends to use reactiveVal()
because the syntax makes it’s clear that something weird is going on.
It’s important to note that both types of reactive values have so called reference semantics. Most R objects have copy-on-modify semantics which means that if you assign the same value to two names, the connection is broken as soon as you modify one.
R Code 15.2 : Copy on modify semantics in R
Reactive values do not have copy-on-modify semantics. They always keep a reference back to the same value so that modifying any copy modifies all values:
R Code 15.3 : Reference semantics of reactive values
We’ll come back to why you might create your own reactive values in Chapter 16. Otherwise, most of the reactive values you’ll encounter will come from the input argument to the server function. These are a little different to the reactiveValues()
that you create yourself because they’re read-only: you can’t modify the values because Shiny automatically updates them based on user actions in the browser.
What are the differences between these two lists of reactive values? Compare the syntax for getting and setting individual reactive values.
reactiveVal()
reference semantics?Design and perform a small experiment to verify that reactiveVal()
also has reference semantics.
Exercise 15.2 : Exercise: Has reactiveVal()
reference semantics?
reactiveVal()
reference semantics?
r1 <- r2 <- reactiveVal(10)
r1(20)
r2()
#> [1] 20
Yes, reactiveVal()
also has reference semantics: I initialized r1 & r2 with reactiveVal(10)
, then I changed only to value of r1(20)
- Printing the content of r2()
results also in 20, although I hadn’t change r2()
directly.
Recall that a reactive has two important properties: it’s lazy and cached. This means that it only does work when it’s actually needed, and if called twice in a row, it returns the previous value.
There are two important details that we haven’t yet covered: what reactive expressions do with errors, and why base::on.exit()
works inside of them.
Reactive expressions cache errors in exactly the same way that they cache values. For example, take this reactive:
R Code 15.4 : Reactive expressions cache errors
Errors are also treated the same way as values when it comes to the reactive graph: errors propagate through the reactive graph exactly the same way as regular values. The only difference is what happens when an error hits an output or observer:
try()
or tryCatch()
.This same system powers req()
(Section 8.1.2), which emits a special type of error. This special error causes observers and outputs to stop what they’re doing but not otherwise fail. By default, it will cause outputs to reset to their initial blank state, but if you use req(..., cancelOutput = TRUE)
they’ll preserve their current display.
on.exit()
You can think of reactive(x())
as a shortcut for function() x()
, automatically adding laziness and caching. This is mostly of importance if you want to understand how Shiny is implemented, but means that you can use functions that only work inside functions. The most useful of these is on.exit()
which allows you to run code when a reactive expression finishes, regardless of whether the reactive successfully returns an error or fails with an error. This is what makes on.exit()
work in Section 8.2.2.
stop()
Use the {reactlog} package to observe an error propagating through the reactives in the following app, confirming that it follows the same rules as value propagation.
R Code 15.5 : Error propagation
stop()
with {reactlog}
I added the line reactlog::reactlog_enable()
to the app code, started the app and ticked the check box “error?”. In the console I received
Warning: Error in <reactive:a>: Error
I started {reactlog} with the Cmd-F3Cmd-F3 shortcut and analyzed the propagation. I could confirm that the error followed the same rules as the value.
req()
Modify the above app to use req()
instead of stop()
. Verify that events still propagate the same way. What happens when you use the cancelOutput
argument?
R Code 15.6 : Error propagation
req()
in the if()
condition does not 3
as in Listing / Output 15.8 out doesn’t output anything. As the condition req() = TRUE
is not fulfilled Shiny stops the execution.req(…, cancelOutput = TRUE)
shows for me no difference. It is said the in the documentation thatit is treated slightly differently if one or more outputs are currently being evaluated. In those cases, the reactive chain does not proceed or update, but the output(s) are left is whatever state they happen to be in (whatever was their last valid state).
But in the reactlog
it shows me the new condition (input$error 'logo TRUE
). But reading the next pargraph in the docs gives an explanation:
Note that this is always going to be the case if this is used inside an output context (e.g.
output$txt <- ...
). It may or may not be the case if it is used inside a non-output context (e.g.reactive()
,observe()
orobserveEvent()
) — depending on whether or not there is anoutput$...
that is triggered as a result of those calls. (emphasis is mine)
Observers and outputs are terminal nodes in the reactive graph. They differ from reactive expressions in two important ways:
cat()
or write.csv()
.Observers and outputs are powered by the same underlying tool: observe()
. This sets up a block of code that is run every time one of the reactive values or expressions it uses is updated. Note that the observer runs immediately when you create it — it must do this in order to determine its reactive dependencies.
R Code 15.7 : Testing behavior of observe()
Hadley rarely uses observe()
in this book, because it’s the low-level tool that powers the user-friendly observeEvent()
. Generally, you should stick with observeEvent()
unless it’s impossible to get it to do what you want. In this book, there is only one case where observe()
is necessary. See Section 16.3.3
observe()
also powers reactive outputs. Reactive outputs are a special type of observers that have two important properties:
output$text <- ...
creates the observer.It’s important to note that observe()
and the reactive outputs don’t “do” something, but “create” something (which then takes action as needed). That mindset helps you to understand what’s going on in this example:
R Code 15.8 : observe()
calls observe()
observe()
calls observe()
x <- reactiveVal(1)
observe({
x()
observe(print(x()))
})
x(2)
x(3)
In contrast to the book example my observe() functions does not assign a value to y
. This generates a > Warning: Error in y: could not find function “y”
Observers do not yield a result, they are only useful for their side effects.
Each change to x
causes the observer to be triggered. The observer itself calls observe()
setting up another observer. So each time x
changes, it gets another observer, so its value is printed another time.
As a general rule, you should only ever create observers or outputs at the top-level of your server function. If you find yourself trying to nest them or create an observer inside an output, sit down and sketch out the reactive graph that you’re trying to create — there’s almost certainly a better approach. It can be harder to spot this mistake directly in a more complex app, but you can always use the reactlog
: just look for unexpected churn in observers (or outputs), then track back to what is creating them.
To finish off the chapter we need to discuss two important tools for controlling exactly how and when the reactive graph is invalidated. In this section, we’ll discuss isolate()
, the tool that powers observeEvent()
and eventReactive()
, and that lets you avoid creating reactive dependencies when not needed. In the next section, you’ll learn about invalidateLater()
which allows you to generate reactive invalidations on a schedule.
isolate()
R Code 15.9 : Observer with infinite loop
If you were to run Listing / Output 15.11, you’d immediately get stuck in an infinite loop because the observer will take a reactive dependency on x
and count
; and since the observer modifies count, it will immediately re-run.
I tried to run Listing / Output 15.11 with two addition: The first one tries to stop the loop after 10 rounds, the second one prints the result for each run.
But this didn’t work. To stop a running process in Shiny is a complex enterprise as I have found out. Searching on the Internet I learned about some pointers to solve this problem. But here I have skipped this non-trivial learning task and prevented to run the infinite loop with eval = false
Resource 15.1 : How to stop running Shiny processes without killing the app
Additionally I found reference to other packages which could use to stop running processes:
Shiny provides isolate()
to resolve the problem of undesired reactive dependency. This function allows you to access the current value of a reactive value or expression without taking a dependency on it:
R Code 15.10 : Accessing reactive value without taking a dependency on it
isolate()
: Accessing reactive values or reactive expressions without taking a dependency on it
r <- reactiveValues(count = 0, x = 1)
r$count
#> [1] 0
class(r)
#> [1] "reactivevalues"
observe({
r$x
r$count <- isolate(r$count) + 1
})
r$x <- 1
r$x <- 2
r$count
#> [1] 0
r$x <- 3
r$count
#> [1] 0
Like observe()
, a lot of the time you don’t need to use isolate()
directly because there are two useful functions that wrap up the most common usage: observeEvent()
and eventReactive()
.
observeEvent()
and eventReactive()
When you saw the code above, you might have remembered Section 3.6 and wondered why we didn’t use observeEvent()
:
R Code 15.11 : Replace observe()
with observeEvent()
observe()
in Listing / Output 15.11 you could use isolate()
as in Listing / Output 15.12, but also observeEvent()
observeEvent(r$x, {
r$count <- r$count + 1
})
Indeed, we could used observeEvent(x, y)
because it is equivalent toobserve({x; isolate(y)})
. It elegantly decouples what you want to listen to from what action you want to take. And eventReactive()
performs the analogous job for reactives: eventReactive(x, y)
is equivalent to reactive({x; isolate(y)})
.
observeEvent()
and eventReactive()
have additional arguments that allow you to control the details of their operation:
ignoreNULL = FALSE
to also handle NULL values.ignoreInit = TRUE
to skip this run.observeEvent()
only, you can use once = TRUE
to run the handler only once.Complete the app below with a server function that updates out
with the value of x
only when the button is pressed.
isolate()
reduces the time the reactive graph is invalidated. This topic of this section, invalidateLater()
does the opposite: it lets you invalidate the reactive graph when no data has changed. You saw an example of this in Section 3.5.1 with reactiveTimer()
, but the time has come to discuss the underlying tool that powers it: invalidateLater()
.
invalidateLater(ms)
causes any reactive consumer to be invalidated in the future, after ms milliseconds. It is useful for creating animations and connecting to data sources outside of Shiny’s reactive framework that may be changing over time. For example, this reactive will automatically generate 10 fresh random normals every half a second:
R Code 15.13 : Observer incrementing number
In the following sections you’ll learn how to use invalidateLater()
to read changing data from disk, how to avoid getting invalidateLater()
stuck in an infinite loop, and some occasionally important details of exactly when the invalidation happens.
A useful application of invalidateLater()
is to connect Shiny to data that is changing outside of R. For example, you could use the following reactive to re-read a csv file every second:
invalidateLater()
```{r}
<- reactive({
data on.exit(invalidateLater(1000))
read.csv("data.csv")
})```
This connects changing data into Shiny’s reactive graph, but it has a serious downside: when you invalidate the reactive, you’re also invalidating all downstream consumers, so even if the data is the same, all the downstream work has to be redone.
To avoid this problem, Shiny provides reactivePoll()
which takes two functions: one that performs a relatively cheap check to see if the data has changed and another more expensive function that actually does the computation. We can use reactivePoll()
to rewrite the previous reactive as below.
reactivePoll()
```{r}
<- function(input, output, session) {
server <- reactivePoll(1000, session,
data function() file.mtime("data.csv"),
function() read.csv("data.csv")
)
}```
Here we used to file.mtime()
, which returns the last time the file was modified, as a cheap check to see if we need to reload the file:
Reading a file when it changes is a common task, so Shiny provides an even more specific helper with reactiveFileReader()
that just needs a file name and a reader function:
reactiveFileReader()
```{r}
<- function(input, output, session) {
server <- reactiveFileReader(1000, session, "data.csv", read.csv)
data
}}
```
If you need to read changing data from other sources (e.g. a database), you’ll need to come up with your own reactivePoll()
code.
If you’re performing a long running computation, there’s an important question you need to consider: when should you execute invalidateLater()
? For example, take this reactive:
invalidateLater()
time problem.
```{r}
<- function(input, output, session) {
server <- reactiveFileReader(1000, session, "data.csv", read.csv)
data
}```
Assume Shiny starts the reactive running at time 0, it will request invalidation at time 500. The reactive takes 1000ms to run, so it’s now time 1000, and it’s immediately invalidated and must be recomputed, which then sets up another invalidation: we’re stuck in an infinite loop.
On the other hand, if you run invalidateLater()
at the end, it will invalidate 500ms after completion, so the reactive will be re-run every 1500 ms.
invalidateLater()
time problem solved.
```{r}
<- reactive({
x on.exit(invalidateLater(500), add = TRUE)
Sys.sleep(1)
10
})```
This is the main reason to prefer invalidateLater()
to the simpler reactiveTimer()
that we used earlier: it gives you greater control over exactly when the invalidation occurs.
The number of milliseconds specified in invalidateLater()
is a polite request, not a demand. R may be doing other things when you asked for invalidation to occur, so your request has to wait. This effectively means that the number is a minimum and invalidation might take longer than you expect. In most cases, this doesn’t matter because small differences are unlikely to affect user perception of your app. However, in situations where many small errors will accumulate, you should compute the exact elapsed time and use it to adjust your calculations.
For example, the following code computes distance based on velocity and elapsed time. Rather than assuming invalidateLater(100)
always delays by exactly 100 ms, we’ll compute the elapsed time and use it in the calculation of position.
Why will this reactive never be executed? Your explanation should talk about the reactive graph and invalidation.
R Code 15.14 : Exercise: A reactive never executed
The reactive is never called. There is no input that can be invalidated and on the other hand, there is no reactive consumer (an output) to display changed values.
After initializing a session, Shiny can’t pick an observer because there is none. As Shiny does not choose a reactive expression for the execution start, and there is no input that could be invalidated, the reactive can’t never be called.
To see the difference I have complemented the app in Listing / Output 15.22 with an output. Now you can see that the reactive is executed.
stopApp()
not working with shinylive-r
I had to use a more complex logic with an intermediate reactive value because the simpler solution with stopApp()
inside an observeEvent()
listening to the “stop” actionButton()
does not work with shinylive-r
.
reactivePoll()
with SQLIf you’re familiar with SQL, use reactivePoll()
to only re-read an imaginary “Results” table whenever a new row is added. You can assume the Results table has a timestamp
field that contains the date-time that a record was added.
As I am not familiar with SQL I will skip this exercise.
term | definition |
---|---|
SQL | Structured Query Language (SQL) (pronounced ess-kew-ell or sequel) is a standardized programming language that is used to manage relational databases and perform various operations on the data in them. Initially created in the 1970s, SQL is regularly used not only by database administrators but also by developers writing data integration scripts and data analysts looking to set up and run analytical queries. For more information see task view on databases <https://cran.r-project.org/web/views/Databases.html>. |
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.
#>
#> ──────────────────────────────────────────────────────────────────────────────