Reactivity

Reactive Programming

Shiny follows a reactive programming paradigm1. We don’t need to command Shiny to update itself, rather, it will react on its own. If an input changes, it will automatically update the outputs dependent upon it.

Think of Shiny as being either energy-saving or lazy (depending on your perspective!). Shiny maximizes the work not done and will only do the most minimal work required to update outputs.

Reactives

In lieu of variables and the typical function that we’d write in a standard R script, R code within the server is often placed inside reactive expressions using reactive({}).

server <- function(input, output) {
  
  # add reactive expression creating a filtered data frame
  filtered_df <- reactive({
    df %>% 
      filter(name == input$name & state == input$state)
  })
  
  output$name_entered <- renderText({
    c("You entered:", input$name)
  })
  
  output$main_table <- renderTable({
    # call reactive expression
    filtered_df()
  })
  
  
}

Reactives help to not repeat ourselves and break-down complex or lengthy code.

Reactives, like standard functions are assigned to a variable and are called upon.

But unlike the average function, they monitor input and dependencies. If reactive sources change, it will update; otherwise, they are lazy.

server <- function(input, output) {
  
  # add reactive expression to munge name before using in filtered_df()
  clean_name <- reactive({
    input$name %>% 
      str_replace_all(" ", "") %>% 
      str_trim() %>% 
      str_to_title()
  })
  
  # call clean_name() in lieu of input$name
  filtered_df <- reactive({
    df %>% 
      filter(name == clean_name() & state == input$state)
  })
  
  output$name_entered <- renderText({
    c("You entered:", input$name)
  })
  
  output$main_table <- renderTable({
    filtered_df()
  })
  
  
}

Observers

In contrast to the lazy reactive({}), observe({}) is eager and also forgetful — they run as soon as they possibly can and they don’t remember their previous action2.

Unlike reactives, observers:

Instead, observers are used primarily for their side-effects.

Side-effects

Sometimes we want our app to perform an action that is beyond our app’s environment. Examples of side-effects can include: writing and saving a file to disk, copying and pasting files, updating a web API, updating a database3, or even updating values of input widgets.

Delaying Response

Specify when a reaction of a reactive or observer should take place with eventReactive({}) or observeEvent({}). The concept is similar to reactive({}) and observe({}), except they will be driven by an event.

eventReactive

The table might render before we even finish typing a name. To delay that rapid response, we can require that an event take place before the app proceeds with certain tasks.

In the example below, an action button is created. The event will be when the user clicks on ‘Enter’.

ui <- fluidPage(
  
  title,
  src,
  
  sidebarLayout(
    sidebarPanel(
      txt_box,
      txt_disp,
      
      selectInput("state",
                  label = 'Select State',
                  choices = unique(df$state),
                  selected = 'Washington'),
      
      # add action button to delay reactivity. Its ID is 'go'.
      actionButton("go", label = "Enter")
    ),
    mainPanel(
      fluidRow(
        column(
          width = 6,
          h3("Table"),
          tbl_disp
        ),
        column(
          width = 6,
          h3("Plot"),
          plotOutput("plot")
        )
      )
    )
  )
  
)

Filtering the data frame and other downstream dependencies will begin once the button is clicked.

server <- function(input, output) {
  
  clean_name <- reactive({
    input$name %>% 
      str_replace_all(" ", "") %>% 
      str_trim() %>% 
      str_to_title()
  })
  
  # change reactive to an eventReactive. Delay reaction until action button is clicked
  filtered_df <- eventReactive(input$go, {
    df %>% 
      filter(name == clean_name() & state == input$state)
  })
  
  output$name_entered <- renderText({
    c("You entered:", input$name)
  })
  
  output$main_table <- renderTable({
    filtered_df()
  })

}

observeEvent

Like observe({}), observeEvent({}) will produce a side-effect; however, it will be triggered only by an event.

A use case of observeEvent is when we want the content of an input widget updated. We can achieve this by nesting an update*() function inside an observeEvent function.

In the example below, another action button is created. When clicked, it will clear the text inside the text box.

ui <- fluidPage(
  
  title,
  src,
  
  sidebarLayout(
    sidebarPanel(
      txt_box,
      
      # add a button to clear text input
      actionButton("clear", label = "Clear Name"),
      
      txt_disp,
      
      selectInput("state",
                  label = 'Select State',
                  choices = unique(df$state),
                  selected = 'Washington'),
      
      actionButton("go", label = "Enter")
    ),
    mainPanel(
      fluidRow(
        column(
          width = 6,
          h3("Table"),
          tbl_disp
        ),
        column(
          width = 6,
          h3("Plot"),
          plotOutput("plot")
        )
      )
    )
  )
  
)

Updating input controls with update*() functions require a session argument. session like input, and output are inherent in Shiny–it’s not something we need to create, we just need to include it as a parameter to the server function. The session object is an environment that can be used to access information and functionality relating to the session4.

# add session parameter in the server function
server <- function(input, output, session) {
  
  clean_name <- reactive({
    input$name %>% 
      str_replace_all(" ", "") %>% 
      str_trim() %>% 
      str_to_title()
  })
  
  # using an observeEvent, update the textInput by clearing the typed name when the 
  # 'Clear Name' button is clicked
  observeEvent(input$clear, {
    updateTextInput(session, 
                    "name",
                    value = ""
    )
  })
 
  filtered_df <- eventReactive(input$go, {
    df %>% 
      filter(name == clean_name() & state == input$state)
  })
  
  output$name_entered <- renderText({
    c("You entered:", input$name)
  })
  
  output$main_table <- renderTable({
    filtered_df()
  })

}

  1. https://mastering-shiny.org/basic-reactivity.html#reactive-programming↩︎

  2. https://mastering-shiny.org/reactivity-objects.html#observers-details↩︎

  3. https://mastering-shiny.org/basic-reactivity.html#observers↩︎

  4. https://shiny.rstudio.com/reference/shiny/latest/session.html↩︎