flowchart LR
D0["input$dataset"] -.->|"read only"| E["dataInput()<br/>eventReactive"]
B["input$load<br/>(button)"] ==>|"trigger"| E
E --> G["output$graph"]
E --> S["output$summary"]
style D0 fill:#2b6c8e,color:#fff
style B fill:#d9534f,color:#fff
style E fill:#f0ad4e,color:#fff
style G fill:#8fd36f,color:#fff
style S fill:#8fd36f,color:#fff
Wait for it: eventReactive()
Deciding when the app reacts
With reactive(), the app reacts immediately to every change. That is not always what we want:
- the user must set several options before the result makes sense (choose a file, then the separator, then the variables…)
- the computation is slow, and we do not want it to start at every click on a menu
We want the app to wait until the user says “go”. The “go” is an actionButton(), and the waiting is done by eventReactive().
The app
Change the dataset: nothing happens. Click the button: now it does.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 650
## file: ui.R
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "dataset",
label = "Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Select dataset")
),
mainPanel(
plotOutput("graph"),
verbatimTextOutput("summary")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# runs ONLY when the button is clicked
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
output$graph <- renderPlot({
data <- dataInput()
plot(data[, 1:2])
})
output$summary <- renderPrint({
data <- dataInput()
summary(data[, 1:2])
})
}
Notice also that before the first click the outputs are empty: at the start the button has never been clicked, so eventReactive() has no value yet, and the outputs that need it wait silently.
The two pieces
# ui.R
1actionButton("load", "Select dataset")- 1
-
A button with ID
load. Remember:input$loadis a counter that increases at every click
# server.R
1dataInput = eventReactive(input$load, {
2 switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})- 1
-
The first argument is the trigger: the code runs only when
input$loadchanges, i.e., when the button is clicked - 2
-
The code to run. It reads
input$dataset, but changing the dataset does not trigger it: its value is simply read at the moment of the click
dataInput() is then used exactly as a reactive(): data = dataInput() inside the render*() functions.
The code, live
The complete code of the app. You can edit it and press ▶.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 650
## file: ui.R
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "dataset",
label = "Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Select dataset")
),
mainPanel(
plotOutput("graph"),
verbatimTextOutput("summary")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# runs ONLY when the button is clicked
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
output$graph <- renderPlot({
data <- dataInput()
plot(data[, 1:2])
})
output$summary <- renderPrint({
data <- dataInput()
summary(data[, 1:2])
})
}
reactive() vs eventReactive()
reactive() |
eventReactive() |
|
|---|---|---|
| Re-runs when… | any input it reads changes | only the trigger changes (e.g., a button) |
| Inputs read inside | are triggers | are only read, at the moment of the trigger |
| At the start | runs as soon as an output needs it | waits for the first trigger |
| You use it with | dataInput() |
dataInput() |
observeEvent()?
You will also find observeEvent(input$button, { ... }). It looks the same, but it returns nothing: it is used to do something (update a menu, show a message, save a file), not to compute a value to use in the outputs. To compute data for the outputs, use eventReactive(). There is an example in Going further.
Your turn
Starting from your reactive() version of the app:
- add an
actionButton("load", "Select dataset")in thesidebarPanel() - turn
reactive({ ... })intoeventReactive(input$load, { ... })
Run the app and check that nothing happens until you click.
See apps/04-eventreactive. Only two lines change compared to the reactive() version:
# ui.R
actionButton("load", "Select dataset")# server.R
dataInput <- eventReactive(input$load, {
...
})In the live app above, add a second button actionButton("draw", "Draw plot") and make only the plot wait for it, while the summary updates as soon as the data are loaded.
Hint: you need a second eventReactive() that is triggered by input$draw and returns dataInput().
# ui.R, in the sidebarPanel()
actionButton("draw", "Draw plot")# server.R
plotData <- eventReactive(input$draw, {
dataInput()
})
output$graph <- renderPlot({
data <- plotData()
plot(data[, 1:2])
})This is the idea of the next page: one button to load the data, one to run the analysis.