This document provides an introduction to reactive programming in Shiny. It discusses key concepts of reactivity including sources, conductors, and endpoints. Reactivity is achieved by re-running expressions when their inputs change. Sources notify dependents of changes using invalidation events, triggering re-runs. Conductors cache intermediate results to improve performance. The isolate function prevents reactivity by using the latest values without re-running on changes. observeEvent and eventReactive allow controlling reactivity by tying it to input triggers rather than automatic re-running on changes.
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Reactive Programming Fundamentals
1. Confidential – do not duplicate or redistribute without permission from xtream srl
An introduction to reactive programming in Shiny
Milan, 13th September 2018
2. 2 Contents
➢ What is reactive programming?
➢ Sources, conductors and endpoints
➢ Preventing reactivity: isolate
➢ observeEvent and eventReactive
3. 1. Road to reactivity
What is reactive programming and why you should care about it
4. 4
A one-way trip (1/3)
> x <- 1
> print(x)
[1] 1
> x <- 2
When the last statement is executed,
the value of x becomes 2, but the
printed value does not change
(obviously!)
5. 5
A one-way trip (2/3)
> x <- 1
> y <- x + 1
> print(y)
[1] 2
> x <- 2
> print(y)
[1] 2
When the value of x changes, the value
of y does not, because the two
variables are not linked together
(obviously!)
6. 6
A one-way trip (3/3)
print(x)
x x
x + 1
In the non-reactive realm, expressions
evaluate their input only when they
need, if an input changes after the
expression has been evaluated, the
result is not affected.
There are no push notifications!
7. 7
The magic reactive world
# app.R
library(shiny)
ui <- fluidPage(
sidebarPanel(
sliderInput("n", label = "input", min = 0, max = 100, value = 50)
),
mainPanel(h1(textOutput("out")))
)
server <- function(input, output) {
output$out <- renderText(input$n)
}
shinyApp(ui = ui, server = server)
8. 8 Up to here…
➢ Non-reactive expressions are lazy: they evaluate their input only when they are run
➢ This means that they cannot react to input changes
➢ Reactive expressions seems to go the other way round, reacting to input changes
➢ This is not how it actually works
9. 9
➢ Reactivity is a feature added on top
of a language, it must follow the rule
of the language
➢ The only way to update the result of
an expression is re-running it
Everything is reactive
If you kindly ask
50Reactive
source
50Reactive
endpoint
10. 10
➢ Reactivity is a feature added on top
of a language, it must follow the rule
of the language
➢ The only way to update the result of
an expression is re-running it
Everything is reactive
If you kindly ask
60
50
Reactive
source
Reactive
endpoint
11. 11
➢ Reactivity is a feature added on top
of a language, it must follow the rule
of the language
➢ The only way to update the result of
an expression is re-running it
Everything is reactive
If you kindly ask
60
50 RE-RUN!
Reactive
source
Reactive
endpoint
12. 12
➢ Reactivity is a feature added on top
of a language, it must follow the rule
of the language
➢ The only way to update the result of
an expression is re-running it
➢ This is what reactivity does. With
this approach, we don’t even need
to know what to re-run. Maybe.
Everything is reactive
If you kindly ask
60
60
Reactive
source
Reactive
endpoint
13. 13
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
Lazyness
The right way
10Reactive
source
30
Reactive
endpoint
20
+
Reactive
conductor
14. 14
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
Lazyness
The right way
10Reactive
source
30
Reactive
endpoint
40
+
Reactive
conductor
15. 15
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
Lazyness
The right way
10Reactive
source
30
Reactive
endpoint
40
+
Reactive
conductor
16. 16
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
Lazyness
The right way
10Reactive
source
30
Reactive
endpoint
40
+
Reactive
conductor
RE-RUN!
17. 17
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
Lazyness
The right way
10Reactive
source
30
Reactive
endpoint
40
+
Reactive
conductor
RE-RUN!
RE-RUN!
18. 18
➢ We cannot re-run everything so fast
that the user does not even perceive
it
➢ We have to be smart
➢ If we implement a proper cache-
invalidation system, we can only re-
run what needs to be. But how?
Lazyness
The right way
10Reactive
source
50
Reactive
endpoint
40
+
Reactive
conductor
19. 19
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
10
+
Reactive
conductor
20. 20
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
21. 21
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
INVALIDATE!
22. 22
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
DIRTY
23. 23
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
INVALIDATE!
24. 24
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
RE-RUN!
25. 25
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
20
Reactive
endpoint
40
+
Reactive
conductor
DIRTY
RE-RUN!
26. 26
➢ Reactive sources and conductor have a
dirty flag, meaning the cached results are
no longer valid
➢ Reactive sources and conductors know
which objects depend on them, and can
make them dirty, though an invalidation
event
➢ When an endpoint gets dirty, it is re-run.
➢ DANGER. If more endpoints get dirty at
the same time, there is no way of knowing
or imposing the execution order
Homing Pigeons
10Reactive
source
50
Reactive
endpoint
40
+
Reactive
conductor
27. 27
➢ When a reactive expression parent
executes another one child, it leaves there
an homing pigeon, capable of reaching
the parent again
➢ When the child gets dirty, it frees the
homing pigeon
➢ When the homing pigeon gets to the
parent, it makes it dirty.
➢ This is why you cannot use a reactive
expression outside a reactive context.
Nobody leaves the pigeon.
Homing Pigeons
Why this title?
28. 28 Up to here…
➢ Reactivity is nothing but a smart way of re-running expressions
➢ Results are cached, so that you don’t re-run something if you already have its result
➢ Reactivity uses messages: reactive expressions know which ones need their result
and can ask them to re-run them if their result changes
➢ We don’t need to go deeper in the implementation details
30. 30
➢ Instances of class reactivevalues
➢ Created by reactiveValues or
reactiveVal
➢ The default object input is a
collection of reactive sources
➢ When they change, they invalidate all
the objects which depend on them
Reactive sources
Reactive
source
31. 31
➢ Created by render* or observe*
functions – e.g. renderText,
observeEvent, …
➢ The default object output is a
collection of reactive endpoints
➢ When invalidated, they re-run within a
negligible time.
➢ They do not return values, but they
perform actions – e.g. writing a log file,
updating the ui, …
Reactive endpoints
Reactive
endpoint
32. 32
➢ Special functions created by the
*reactive functions (e.g.
reactive, eventReactive, …)
➢ Functions: they must return some
value. The result of the last
expression is returned by default
➢ When invalidated, they invalidate all
the conductors and the endpoints
which depend on them
Reactive conductors
Reactive
conductor
36. 36
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
10Reactive
source
30
Reactive
endpoint
20
isolate()
37. 37
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
10Reactive
source
30
Reactive
endpoint
40
isolate()
38. 38
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
20Reactive
source
30
Reactive
endpoint
40
isolate()
DIRTY
39. 39
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
20Reactive
source
30
Reactive
endpoint
40
INVALIDATE!
DIRTY
isolate()
40. 40
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
20Reactive
source
30
Reactive
endpoint
40
RE-RUN!
DIRTY
41. 41
➢ A reactive endpoint depends on two
sources
➢ But you want the endpoint to be
recomputed only if one of them
changes
➢ Of course, you want that, when the
endpoint is re-run, it uses the most
updated value for both the sources
Preventing reactivity
20Reactive
source
60
Reactive
endpoint
40
isolate()
42. 42
Assuming a couple of numeric inputs
n1 and n2 exists:
output$computed = renderText(
input$n1 + isolate(input$n2)
)
Preventing reactivity
The code
20Reactive
source
60
Reactive
endpoint
40
isolate()
44. 44 Why controlling reactivity?
➢ You have a massive application, which let the user analyse data and tune some
parameter of a super-cool machine learning model
➢ Users can train the model against data, but this is a long, long procedure
➢ You don’t want model training to be triggered each time a user changes a
parameter, even if the model naturally depends on them
➢ So, you want to add a button to trigger the training algorithm, but you want training
to use the most updated values of parameters
46. 46
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
numericInput("n", "Input:", value = 10),
actionButton('go', 'Go!')
),
mainPanel(
h1("Result"),
textOutput("computed")
)
)
)
server <- function(input, output) {
step2 = step1 = function(x){
Sys.sleep(1)
x+1
}
output$computed = renderText({
input$go
s1 = isolate(step1(input$n))
isolate(step2(s1))
})
}
shinyApp(ui = ui, server = server)
➢ An actionButton returns a
number which is incremented by 1
each time it is pressed
➢ We take a reactive dependency
from go and we isolate all the rest
➢ This is not handy, nor readable. Why
should we use a reactive context if
we isolate all the inputs but one?
Controlling reactivity
A first attempt
47. 47
server <- function(input, output) {
step2 = step1 = function(x){
Sys.sleep(1)
x+1
}
res = eventReactive(
input$go, {
s1 = step1(input$n)
step2(s1)
})
output$computed = renderText(
res()
)}
shinyApp(ui = ui, server = server)
Controlling reactivity
The right way
goReactive
source
computed
Reactive
endpoint
n
isolate()
res
48. 48
➢ eventReactive has two main
arguments:
➢ A reactive expression to take
dependency on
➢ An expression to run when the first
argument gets invalidated
➢ It is a reactive conductor: it returns a
value
➢ Under the hood, it translate almost
exactly in the first example
➢ But this is more straightforward and
readable!
Controlling reactivity
The right way
server <- function(input, output) {
step2 = step1 = function(x){
Sys.sleep(1)
x+1
}
res = eventReactive(
input$go, {
s1 = step1(input$n)
step2(s1)
})
output$computed = renderText(
res()
)}
shinyApp(ui = ui, server = server)
49. 49
server <- function(input, output) {
write_log_2 = write_log_1 =
function(x){
Sys.sleep(1)
print(x)
}
observeEvent(
input$go,
{
write_log_1(input$n)
write_log_2(input$n)
})
}
Controlling reactivity
The right way
goReactive
source
Reactive
endpoint
n
isolate()
50. 50
➢ observeEvent has two main
arguments:
➢ A reactive expression to take
dependency on
➢ An expression to run when the
first argument gets invalidated
➢ It is not a reactive conductor, but a
reactive endpoint. It does not return
any value.
Controlling reactivity
The right way
server <- function(input, output) {
write_log_2 = write_log_1 =
function(x){
Sys.sleep(1)
print(x)
}
observeEvent(
input$go,
{
write_log_1(input$n)
write_log_2(input$n)
})
}
51. 51
Use observeEvent whenever you want
to perform an action in response to an
event. Note that "recalculate a value"
does not generally count as performing
an action – see eventReactive for
that.
Use eventReactive to create a
calculated value that only updates in
response to an event.
This is just like a normal reactive
expression except it ignores all the
usual invalidations that come from its
reactive dependencies; it only
invalidates in response to the given
event.
observeEvent eventReactive
observeEvent vs
eventReactive
Source: https://shiny.rstudio.com/reference/shiny/1.0.0/observeEvent.html
52. 52 Resources
The best place to start learning Shiny is the documentation home page:
https://shiny.rstudio.com/tutorial/
Here you can find lots of pointers to articles, videos and tutorials.