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:

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$load is 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$load changes, 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()

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

And 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

Exercise 1 · In your project

Starting from your reactive() version of the app:

  1. add an actionButton("load", "Select dataset") in the sidebarPanel()
  2. turn reactive({ ... }) into eventReactive(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, {
  ...
})
Exercise 2 · Two buttons, two moments

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.