Updating inputs

observeEvent() and updateSelectInput()

In Your app: explore a dataset the X and Y menus were created by the server with renderUI(). There is another way: the menus exist in ui.R from the start, empty, and the server updates their choices when the data are loaded.

Try it: load a dataset and look at how the X and Y menus change.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 700
## file: ui.R
library(shiny)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("dataset", "Choose a dataset:",
                  choices = c("rock", "pressure", "cars")),
      actionButton("load", "Load dataset"),
      hr(),
      # the menus exist from the start, but they are empty
      selectInput("x", "X variable:", choices = NULL),
      selectInput("y", "Y variable:", choices = NULL),
      actionButton("analyze", "Update analysis")
    ),
    mainPanel(
      plotOutput("graph"),
      verbatimTextOutput("summary")
    )
  )
)

## file: server.R
library(shiny)

server <- function(input, output, session) {   # session is needed by update*()

  dataInput <- eventReactive(input$load, {
    switch(input$dataset,
           rock = rock,
           pressure = pressure,
           cars = cars)
  })

  # observeEvent: DO something (a side effect) when the data change.
  # It returns nothing: it updates the menus that already exist in the UI
  observeEvent(dataInput(), {
    vars <- names(dataInput())
    updateSelectInput(session, "x", choices = vars, selected = vars[1])
    updateSelectInput(session, "y", choices = vars,
                      selected = vars[min(2, length(vars))])
  })

  # freeze data + chosen variables when "Update analysis" is clicked
  analysisInput <- eventReactive(input$analyze, {
    req(input$x, input$y)
    list(data = dataInput(), x = input$x, y = input$y)
  })

  output$graph <- renderPlot({
    obj <- analysisInput()
    plot(obj$data[[obj$x]], obj$data[[obj$y]],
         xlab = obj$x, ylab = obj$y, pch = 19)
  })

  output$summary <- renderPrint({
    obj <- analysisInput()
    summary(obj$data[, c(obj$x, obj$y)])
  })
}

How it works

# ui.R
1selectInput("x", "X variable:", choices = NULL)
1
A normal menu, with no choices yet
# server.R
1server <- function(input, output, session) {

2  observeEvent(dataInput(), {
    vars <- names(dataInput())
3    updateSelectInput(session, "x", choices = vars)
  })
}
1
session is needed: it tells update*() which user’s app to change
2
When dataInput() changes, do something. observeEvent() returns nothing: it is used for its side effects
3
Change the choices of the existing menu with ID x

Every input has its update*() function: updateSliderInput(), updateTextInput(), updateNumericInput()…

The code

#| '!! 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: 700
## file: ui.R
library(shiny)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("dataset", "Choose a dataset:",
                  choices = c("rock", "pressure", "cars")),
      actionButton("load", "Load dataset"),
      hr(),
      # the menus exist from the start, but they are empty
      selectInput("x", "X variable:", choices = NULL),
      selectInput("y", "Y variable:", choices = NULL),
      actionButton("analyze", "Update analysis")
    ),
    mainPanel(
      plotOutput("graph"),
      verbatimTextOutput("summary")
    )
  )
)

## file: server.R
library(shiny)

server <- function(input, output, session) {   # session is needed by update*()

  dataInput <- eventReactive(input$load, {
    switch(input$dataset,
           rock = rock,
           pressure = pressure,
           cars = cars)
  })

  # observeEvent: DO something (a side effect) when the data change.
  # It returns nothing: it updates the menus that already exist in the UI
  observeEvent(dataInput(), {
    vars <- names(dataInput())
    updateSelectInput(session, "x", choices = vars, selected = vars[1])
    updateSelectInput(session, "y", choices = vars,
                      selected = vars[min(2, length(vars))])
  })

  # freeze data + chosen variables when "Update analysis" is clicked
  analysisInput <- eventReactive(input$analyze, {
    req(input$x, input$y)
    list(data = dataInput(), x = input$x, y = input$y)
  })

  output$graph <- renderPlot({
    obj <- analysisInput()
    plot(obj$data[[obj$x]], obj$data[[obj$y]],
         xlab = obj$x, ylab = obj$y, pch = 19)
  })

  output$summary <- renderPrint({
    obj <- analysisInput()
    summary(obj$data[, c(obj$x, obj$y)])
  })
}

req(): wait for what you need

In this app analysisInput() starts with req(input$x, input$y). req() checks that its arguments are “available” (not NULL, not empty, not FALSE…). If they are not, it stops the computation silently: no red error, the output simply waits.

Use it whenever an output needs something that may not be there yet: an uploaded file, a selection, a click.

eventReactive() vs observeEvent()

eventReactive() observeEvent()
Returns a value, used as x() nothing
Used for computing data for the outputs doing things: updating inputs, showing messages, saving
Runs when an output needs it (lazy) always, at every trigger (eager)