Skip to content

Instantly share code, notes, and snippets.

@dwill023
Created February 19, 2025 20:18
Show Gist options
  • Select an option

  • Save dwill023/8edffd44b5e228cf2b52dec13483f189 to your computer and use it in GitHub Desktop.

Select an option

Save dwill023/8edffd44b5e228cf2b52dec13483f189 to your computer and use it in GitHub Desktop.

Reactive triggers

Good use cases for reactive triggers.

  • expensive or time-consuming process: Instead of automatically recalculating data whenever an input changes, you might want to let users decide when to update an expensive or time‐consuming computation (e.g., querying an external API). A reactive trigger lets you decouple the immediate input change from the computation, giving the user control over when the data is refreshed.
  • Batch Updating: In scenarios where several inputs might change in quick succession, you may not want each change to individually trigger a recalculation. Instead, you can wait until all inputs are set (perhaps with a "Submit" or "Apply Changes" button) and then use a reactive trigger to perform a single, coordinated update.
  • Conditional Recalculation: update only under certain conditions. Using a trigger lets you isolate the reactivity to those conditions rather than having it automatically tied to every related input.
  • Periodic or Scheduled Updates: You can combine a reactive trigger with a timer (e.g., using invalidateLater()) to force periodic updates of a component. This approach is useful for dashboards that need to refresh data on a regular interval without relying solely on user input.

The below example shows even though input$choice is used to determine the result, it is wrapped with isolate(). This prevents changes to the radio button from triggering an immediate recalculation. Only clicking Refresh Calculation (which calls trigger$trigger()) forces the expensive computation to run again.

library(shiny)

# Define a custom reactive trigger
# https://github.com/daattali/advanced-shiny/tree/master/reactive-trigger
makeReactiveTrigger <- function() {
  # Create a reactive value to signal changes
  rv <- reactiveVal(0)
  list(
    # Call this inside reactive contexts to create a dependency.
    depend = function() {
      rv()
    },
    # Call this to trigger all dependencies.
    trigger = function() {
      rv(rv() + 1)
    }
  )
}

ui <- fluidPage(
  titlePanel("Expensive Calculation with Reactive Trigger"),
  sidebarLayout(
    sidebarPanel(
      radioButtons("choice", "Select an option:",
                   choices = c("Option A", "Option B")),
      actionButton("refresh", "Refresh Calculation")
    ),
    mainPanel(
      textOutput("expensiveCalc")
    )
  )
)

server <- function(input, output, session) {
  
  # Create an instance of the reactive trigger.
  trigger <- makeReactiveTrigger()
  
  # When the refresh button is clicked, trigger the update.
  observeEvent(input$refresh, {
    trigger$trigger()
  })
  
  # Expensive calculation that uses the trigger.
  # Notice that we "isolate" input$choice so that changes to it don't cause recalculation.
  expensiveData <- reactive({
    # Establish dependency on the trigger.
    trigger$depend()
    # Capture the current radio button value (without making it a reactive dependency).
    currentChoice <- isolate(input$choice)
    # Simulate an expensive computation (e.g., a long calculation or data processing).
    Sys.sleep(2)  # simulate delay
    paste("Computed result for", currentChoice, ":", sample(1:100, 1))
  })
  
  # Display the result.
  output$expensiveCalc <- renderText({
    expensiveData()
  })
}

shinyApp(ui, server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment