Server: Reactive programming

Sources: Wickham (2021, Ch. 3)

1 Basics

  • In Shiny, server logic is expressed using reactive programming (elegant powerful programming paradigm!)
  • Very different to writing script that runs from start to end
  • Key idea: specify a graph of dependencies so that when an input changes, all related outputs are automatically updated
  • Reactive programming makes flow of an app considerably simpler

2 The server function

  • “Guts” of every shiny app below
library(shiny)

# front end interface (Html)
ui <- fluidPage()

# back end logic
server <- function(input, output, session) {}

shinyApp(ui, server)
1
User interface
2
Server
  • ui simple because every user gets same html
  • server more complicated because every user needs independent version of the app
    • e.g., Julia using slider should not affect Petra ui!
  • server() is invoked each time new session starts
    • 3 parameters (input, output, session) that are created by Shiny (not by us!) when session starts connecting to specific session

3 Input & output (lists)

  • input: a list-like object that contains all the input data sent from the browser, named according to the input ID
    • e.g., numericInput("count", label = "Number of values", value = 100) generates input$count
    • input can only be read from within reactive contexts created by a reactive functions like renderText() or reactive()
      • reactive functions allow for outputs to automatically update when an input changes
  • output: a list-like object containing outputs named according to output ID
    • Difference: output used for sending output instead of receiving input (always in concert with render function as below)
    • Q: How many inputs/outputs/render functions are there in the code below? What does it do?
Simple input/output example
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste0("Hello ", input$name, "!")
  })
}
shinyApp(ui, server)
1
render functions, e.g., renderText() set up special reactive context that automatically tracks what inputs the output uses AND converts output of R code into HTML suitable for display on a web page

4 Render functions

  • render functions, located in server, wrap generated outputs and correspond to the type or reactive output
    • resulting values are stored in output$... list
  • Q: What do you think are the following render functions used for? (e.g., )
    • renderImage({...})
    • renderPlot({...})
    • renderPlotly({...}) (!)
    • renderPrint({...})
    • renderTable({...})
    • renderDataTable({...}) (!)
    • renderText({...})
    • renderUI({...}) (!)
    • renderLeaflet({...}) (!)
  • Our Guerry app uses those marked with (!).
  • renderImage({...}) creates images (saved as a link to a source file)
  • renderPlot({...}) creates plots
  • renderPlotly({...}) creates interactive plotly graph
  • renderPrint({...}) creates any printed output
  • renderTable({...}) creates data frame, matrix, other table like structures
    • renderDataTable({...}) creates interactive datatable
  • renderText({...}) creates character strings
  • renderUI({...}) creates a Shiny tag object or HTML
  • renderLeaflet({...}) create a leaflet map

5 Reactive programming

5.1 How does reactivity work?

  • Q: How does reactivity work? What does the app below do? (Let’s run it too!)
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste0("Hello ", input$name, "!")
  })
}
shinyApp(ui, server)
  • Shiny performs the renderText() action every time we update input$name (automatically!)
  • reactive refers to any expression that automatically updates itself when its dependencies change
  • Important: Code informs Shiny how it could create the string if it needs to, but it’s up to Shiny when (and even if!) the code should be run
  • Recipe: App provides Shiny with recipe (not commands) what to do with inputs

5.2 The reactive graph

  • Usually R code can be read from top to bottom (= order of execution)… not in Shiny!
  • Reactive graph: describes how inputs and outputs are connected to understand order of execution
  • Figure 1 describes app in Section 5.1 above.
    • tells that output$greeting will need to be recomputed whenever input$name is changed
    • greeting has a reactive dependency on name

Figure 1: The reactive graph shows how the inputs and outputs are connected (Source: Wickham 2021)
  • Quick high-level sketch of reactive graphs help to understand how pieces fit together

5.3 Reactive expressions

  • Reactive expressions take inputs and produce outputs
    • can reduce duplication in reactive code by introducing additional nodes into reactive graph
    • Figure 2 contains reactive expression string (inspect shape!) with code shown below

Figure 2: A reactive expression is drawn with angles on both sides because it connects inputs to outputs (Source: Wickham 2021)
  • Below string is created with reactive() function to app in Section 5.1.
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting"),
)

server <- function(input, output, session) {
  string <- reactive(paste0("Hello ", input$name, "!"))
  output$greeting <- renderText(string())
}
shinyApp(ui, server)
  • Avoid duplication
    • Q: How does the code below avoid duplicating code?
Show the code
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting"),
  textOutput("greeting2")
)

server <- function(input, output, session) {
  string <- reactive(paste0("Hello ", input$name, "!"))
  output$greeting <- renderText(string())
  output$greeting2 <- renderText(string())
}
shinyApp(ui, server)

5.4 Executation order

  • Order Shiny code is run is solely determined by reactive graph
  • Below we flip code in server function below
    • Better keep order for easier understanding!
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting"),
)

server <- function(input, output, session) {
  output$greeting <- renderText(string())
  string <- reactive(paste0("Hello ", input$name, "!"))
}
shinyApp(ui, server)

5.5 Exercises

  1. Can you spot errors in the code of the different server1, server2 and server3 below?
# UI
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

# SERVERS
server1 <- function(input, output, server) {
  input$greeting <- renderText(paste0("Hello ", name))
}

# HOMEWORK!
server2 <- function(input, output, server) {
  greeting <- paste0("Hello ", input$name)
  output$greeting <- renderText(greeting)
}

server3 <- function(input, output, server) {
  output$greting <- paste0("Hello", input$name)
}
  1. Draw the reactive graph for the following three server functions (what are the inputs, reactives and ouputs): (Homework: server2 and server3!)

Start by deciding how many and which inputs (1), reactives (2) and ouputs (3) there are. Then start drawing with inputs represented in the first column on the left. You could use, e.g., name> for inputs, >name> for reactives and >name for outputs and arrows to connect them.

server1 <- function(input, output, session) {
  c <- reactive(input$a + input$b)
  e <- reactive(c() + input$d)
  output$f <- renderText(e())
}

server2 <- function(input, output, session) {
  x <- reactive(input$x1 + input$x2 + input$x3)
  y <- reactive(input$y1 + input$y2)
  output$z <- renderText(x() / y())
}

server3 <- function(input, output, session) {
  d <- reactive(c() ^ input$d)
  a <- reactive(input$a * 10)
  c <- reactive(b() / input$c) 
  b <- reactive(a() + input$b)
}
  1. Can you spot errors in the code of the different server1, server2 and server3 below?
  • server1: Forgot input$
  • server2: input$name outside of renderText() function
  • server3: Typo in output$greting




  1. Draw the reactive graph for the following three server functions (Solution source):

To create the reactive graph we need to consider the inputs, reactive expressions, and outputs of the app.

For server1 we have the following objects:

  • inputs: input$a, input$b, and input$d
  • reactives: c() and e()
  • outputs: output$f

Inputs input$a and input$b are used to create c(), which is combined with input$d to create e(). The output depends only on e().

reactive graph - server 1


For server2 we have the following objects:

  • inputs: input$y1, input$y2, input$x1, input$x2, input$x3
  • reactives: y() and x()
  • outputs: output$z

Inputs input$y1 and input$y2 are needed to create the reactive y(). In addition, inputs input$x1, input$x2, and input$x3 are required to create the reactive x(). The output depends on both x() and y().

reactive graph - server 2


For server3 we have the following objects:

  • inputs: input$a, input$b, input$c, input$d
  • reactives: a(), b(), c(), d()

As we can see below, a() relies on input$a, b() relies on both a() and input$b, and c() relies on both b() and input$c. The final output depends on both c() and input$d.

reactive graph - server 3


6 Reactive expressions (more!)

  • Chapter 3.4 is recommended reading!
  • Reactive expressions (e.g., reactive()) are important because…
    • give Shiny more information so that it can do less recomputation when inputs change
    • make apps more efficient and easier for humans to understand (simplify reactive graph!)
  • Are like inputs since you can use results of a reactive expression in an output
  • Are like outputs since they depend on inputs and automatically know when they need updating
  • Inputs and reactive expressions are reactive producers (see Figure 3)
  • Reactive expressions and outputs are reactive consumers (see Figure 3)

Figure 3: Inputs and expressions are reactive producers; expressions and outputs are reactive consumers (Source: Wickham 2021)

7 Reactive functions: Overview

  • Shiny provides a variety of reactive functions such as reactive(), observe(), bindevent() and others.`
  • See insightful discussions on reactivity and reactive functions in Chapter 3.5.1.

7.1 reactive()

  • reactive(): wraps a normal expression to create a reactive expression
    • is “reactive” in the sense that if its dependencies change, it will automatically update.
    • Below reactive string changes whenever dependency input$name changes.
    • Q: What would be the reactive producer and what the reactive consumer?
ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting"),
)

server <- function(input, output, session) {
  string <- reactive(paste0("Hello ", input$name, "!"))
  output$greeting <- renderText(string())
}
shinyApp(ui, server)
  })

7.2 observe() vs. reactive()

  • reactive(): creates a reactive expression that can be changed over time by user inputs
  • observe(): creates an observer that runs whenever any of its reactive dependencies change
    • i.e., code inside observe() will be re-evaluated whenever any reactive inputs or reactive expressions that it references get updated
    • BUT we don’t assign result of observe() to a variable, so we can’t refer to it from other reactive consumers

Below we use a reactive expression using reactive() to create squared. This is then reused in the observe() function that wraps a render function renderText() that creates and the output element output$text.

library(shiny)

ui <- fluidPage(
  numericInput("num", "Enter a number", value = 1),
  textOutput("text")
)

server <- function(input, output) {
  # reactive expression
  squared <- reactive({ 
    input$num^2 
  })
  
  # observer
  observe({ 
    output$text <- renderText({
      paste0("The square of ", input$num, " is ", squared())
    })
  })
}

shinyApp(ui = ui, server = server)

7.3 Reacting on events: bindEvent()

  • bindEvent(): provides a straightforward API for event handling
  • observeEvent() (observers): used when you want to perform an action in response to an event (see input$button below), but you don’t need the result of the action to be used in the UI

In this example, when the “Generate Random Number” button is clicked, a random number is generated, but it doesn’t get displayed immediately. Instead, the output$randomNumber expression is bound to input$dispButton event with bindEvent(), and the result is displayed only when the “Display Random Number” button is clicked. This allows you to have more control over when the UI updates in response to changes in server-side reactive values.

library(shiny)

# Define UI
ui <- fluidPage(
    actionButton("genButton", "Generate Random Number"),
    actionButton("dispButton", "Display Random Number"),
    textOutput("randomNumber")
)

# Define server logic
server <- function(input, output) {
    randNum <- reactiveValues(num = NULL)

    observeEvent(input$genButton, {
        randNum$num <- runif(1) # Generate a random number when genButton is clicked
    })
    
    output$randomNumber <- renderText({ 
        randNum$num # Generate the reactive expression
    }) %>% 
    bindEvent(input$dispButton) # Binding the output$randomNumber reactive expression to dispButton
}

# Run the application 
shinyApp(ui = ui, server = server, options = list(display.mode='showcase'))

7.4 eventReactive() (skip!)

  • Alternative to bind_event()
  • eventReactive(): Similar to reactive(), but only re-evaluates when a certain event is triggered. Can be used in combination with observeEvent().

In this app, when you click the “Generate Random Number” button, a random number is generated, but it’s not displayed yet. When you click the “Display Random Number” button, the generated number is then displayed. The eventReactive() function is used to create a reactive value (the random number) that is updated only when a specific event (clicking the “Generate Random Number” button) occurs.

library(shiny)

# Define UI
ui <- fluidPage(
    actionButton("genButton", "Generate Random Number"),
    actionButton("dispButton", "Display Random Number"),
    textOutput("randomNumber")
)

# Define server logic
server <- function(input, output) {
    randNum <- eventReactive(input$genButton, {
        runif(1) # Generate a random number when genButton is clicked
    })
    
    observeEvent(input$dispButton, {
        output$randomNumber <- renderText({ randNum() }) # Display the random number when dispButton is clicked
    })
}

# Run the application 
shinyApp(ui = ui, server = server, options = list(display.mode='showcase'))

7.5 isolate() (skip!)

  • isolate(): used to access the value of a reactive expression or input without setting up a dependency
    • useful to access the current value of an input or reactive expression, but without re-running the code when that input or expression changes

In this app, when you click the “Generate Random Number” button, a random number is generated. This number does not immediately cause a reactive event because it’s isolated within the isolate() function. It only gets displayed when you click the “Display Random Number” button. Changes to randNum$num after “Display Random Number” button is clicked won’t affect the displayed value until the button is clicked again. Thus, the isolate() function enables the use of reactive values without triggering reactivity.

library(shiny)

# Define UI
ui <- fluidPage(
    actionButton("genButton", "Generate Random Number"),
    actionButton("dispButton", "Display Random Number"),
    textOutput("randomNumber")
)

# Define server logic
server <- function(input, output) {
    randNum <- reactiveValues(num = NULL) # Create object to store reactiv values

    observeEvent(input$genButton, {
        randNum$num <- runif(1) # Generate a random number when genButton is clicked
    })
    
    observeEvent(input$dispButton, {
        output$randomNumber <- renderText({ 
          isolate(randNum$num) # Display the random number when dispButton is clicked, but do not reactivity link it
        }) 
    })
}

# Run the application 
shinyApp(ui = ui, server = server, options = list(display.mode='showcase'))

7.6 reactiveTimer() (skip!)

  • reactiveTimer(): used to create a reactive expression that invalidates itself after a given number of milliseconds. This can be useful for causing certain parts of your Shiny app to update on a regular interval.

In this example, autoInvalidate() is a reactive expression that becomes invalidated (i.e., signals that it needs to be re-evaluated) every 1000 milliseconds. By referencing autoInvalidate() inside the renderText() function, we’re creating a dependency — so, every time autoInvalidate() is invalidated, the current time is re-evaluated and the UI is updated with the new time.

library(shiny)

# Define UI
ui <- fluidPage(
  textOutput("currentTime")
)

# Define server logic
server <- function(input, output) {
  
  # Define a reactive timer with a 1000ms (1s) interval
  autoInvalidate <- reactiveTimer(1000)
  
  output$currentTime <- renderText({
    autoInvalidate()  # This line causes the reactive expression to be invalidated (and thus re-evaluated) every second
    as.character(Sys.time())  # Display the current time
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

8 Guerry app (reactivity): Tabulate data tab

  • The Guerry app includes the following reactive functions: reactive(), observe(), isolate(), bindEvent().
  • Below the basic code underlying the Tabulate tab of our app.
    • What would the reactive graph look like for this app (how many inputs, reatives, outputs)?
    • Which reactive functions can you identify?
R code underlying tabulate tab
library(shiny)
library(htmltools)
library(bs4Dash)
library(fresh)
library(waiter)
library(shinyWidgets)
library(Guerry)
library(sf)
library(tidyr)
library(dplyr)
library(RColorBrewer)
library(viridis)
library(leaflet)
library(plotly)
library(jsonlite)
library(ggplot2)
library(GGally)
library(datawizard)
library(parameters)
library(performance)
library(ggdark)
library(modelsummary)

# 1 Data preparation ----

## Load & clean data ----
variable_names <- list(
  Crime_pers = "Crime against persons",  
  Crime_prop =  "Crime against property",  
  Literacy = "Literacy",  
  Donations = "Donations to the poor",  
  Infants = "Illegitimate births",  
  Suicides = "Suicides",  
  Wealth = "Tax / capita",  
  Commerce = "Commerce & Industry",  
  Clergy = "Clergy",  
  Crime_parents = "Crime against parents",  
  Infanticide = "Infanticides",  
  Donation_clergy = "Donations to the clergy",  
  Lottery = "Wager on Royal Lottery",  
  Desertion = "Military desertion",  
  Instruction = "Instruction",  
  Prostitutes = "Prostitutes",  
  Distance = "Distance to paris",  
  Area = "Area",  
  Pop1831 = "Population"
)

data_guerry <- Guerry::gfrance85 %>%
  st_as_sf() %>%
  as_tibble() %>%
  st_as_sf(crs = 27572) %>%
  mutate(Region = case_match(
    Region,
    "C" ~ "Central",
    "E" ~ "East",
    "N" ~ "North",
    "S" ~ "South",
    "W" ~ "West"
  )) %>%
  select(-c("COUNT", "dept", "AVE_ID_GEO", "CODE_DEPT")) %>%
  select(Region:Department, all_of(names(variable_names)))



## Prep data (Tab: Tabulate data) ----
data_guerry_tabulate <- data_guerry %>% 
  st_drop_geometry() %>% 
  mutate(across(.cols = all_of(names(variable_names)), round, 2))



# 3 UI ----

ui <- dashboardPage(
  title = "The Guerry Dashboard",

  ## 3.1 Header ----
  header = dashboardHeader(
    title = tagList(
      span("The Guerry Dashboard", class = "brand-text")
    )
  ),
  ## 3.2 Sidebar ----
  sidebar = dashboardSidebar(
    id = "sidebar",
    sidebarMenu(
      id = "sidebarMenu",
      menuItem(tabName = "tab_tabulate", text = "Tabulate data", icon = icon("table")),
      flat = TRUE
    ),
    minified = TRUE,
    collapsed = TRUE,
    fixed = FALSE,
    skin = "light"
  ),
  ## 3.3 Body ----
  body = dashboardBody(
    tabItems(
      ### 3.3.2 Tab: Tabulate data ----
      tabItem(
        tabName = "tab_tabulate",
        fluidRow(
          #### Inputs(s) ----
          pickerInput(
            "tab_tabulate_select",
            label = "Filter variables",
            choices = setNames(names(variable_names), variable_names),
            options = pickerOptions(
              actionsBox = TRUE,
              windowPadding = c(30, 0, 0, 0),
              liveSearch = TRUE,
              selectedTextFormat = "count",
              countSelectedText = "{0} variables selected",
              noneSelectedText = "No filters applied"
            ),
            inline = TRUE,
            multiple = TRUE
          )
        ),
        hr(),
        #### Output(s) (Data table) ----
        DT::dataTableOutput("tab_tabulate_table")
      )
    ) # end tabItems
  )
)



# 4 Server ----

server <- function(input, output, session) {
  
  ## 4.1 Tabulate data ----
  ### Variable selection ----
  tab <- reactive({
    var <- input$tab_tabulate_select
    data_table <- data_guerry_tabulate
    
    if (!is.null(var)) {
      data_table <- data_table[, c("Region", "Department",var)]
    }
    
    data_table
  })
  
  
  ### Create table----
  dt <- reactive({
    tab <- tab()
    ridx <- ifelse("Department" %in% names(tab), 3, 1)
    DT::datatable(
      tab,
      class = "hover",
      extensions = c("Buttons"),
      selection = "none",
      filter = list(position = "top", clear = FALSE),
      style = "bootstrap4",
      rownames = FALSE,
      options = list(
        dom = "Brtip",
        deferRender = TRUE,
        scroller = TRUE,
        buttons = list(
          list(extend = "copy", text = "Copy to clipboard"),
          list(extend = "pdf", text = "Save as PDF"),
          list(extend = "csv", text = "Save as CSV"),
          list(extend = "excel", text = "Save as JSON", action = DT::JS("
          function (e, dt, button, config) {
            var data = dt.buttons.exportData();
  
            $.fn.dataTable.fileSave(
              new Blob([JSON.stringify(data)]),
              'Shiny dashboard.json'
            );
          }
        "))
        )
      )
    )
  })
  
  ### Render table----
  output$tab_tabulate_table <- DT::renderDataTable(dt(), server = FALSE)
  
  

}

shinyApp(ui, server)

9 Loading things in Shiny

9.1 When is code run?

  • When is code in a shiny app run? (Source)

  • Code outside of ui and server is run once, when the app is launched.

  • Code inside the server function is run once each time a user visits the app (opens the webpage).

  • Code inside render functions is run each time a user changes a widget (input$...) that ouput$... depends on

  • Q: So where shall we put the function to load the datasets?
  • Q: What problem might occur if we place certain code wrongly ? Where would you place data management tasks?
  • If possible place anything computationally intensive outside of the render functions.
    • e.g., might make sense to estimate models/subset data beforehand if possible and access precalculated objects in reactive functions

9.2 Where to load things

  • Code outside server <- function(input, output) {} is run once, when you launch your app
  • Code inside server <- function(input, output) {} is run once each time a user visits your app
  • Code inside render* functions is rerun constantly (not only when user changes widget value ( see reactivity)
  • That means…
    • Load Source scripts, libraries, and data outside of server function (at the beginning)
      • Store data in www/ folder in your app directory
      • Access with read.table("www/swiss.csv", sep=",")
      • Access online data by inserting the url into the read* function (e.g. read.table())
    • User specific objects (e.g. object that records user’s session information) are defined inside shinyServer’s unnamed function, but outside of any render* calls
      • e.g. user registers himself, user data as input data (compare income)
    • Code/objects that are affected by choices in widgets must be placed within the a render* function
      • Shiny reruns code in a render* chunk each time a user changes a widget mentioned in the chunk
  • Avoid placing code within render function that does not need to be there… for performance reasons!

10 Data storage

  • Things might get tricky for more data-hungry Shiny apps
  • The way data is stored and accessed has some important implications for
    • Memory allocation: R stores objects in the working memory
    • Performance: “R does too much” - Colin Fay
    • Readability: Putting everything in one file might get messy
  • For more sophisticated setups: databases (e.g., SQLite, PostgreSQL, MongoDB)
  • R can work perfectly well with database connections (R Packages: DBI, dbplyr, sf) (see overview here)

11 Summary

To build reactive shiny apps…

  • Use *Output functions to place reactive objects in the UI (webpage)
  • Use render* functions to let R build output objects (on the server)
    • Render functions are located in server <- function(input, output) {...})
    • R expressions are surrounded by braces, {} in render* functions
    • Outputs of render* are saved in the output list, with one entry for each reactive object in your app
    • Reactivity by including an input values in a render* expression
  • Often times you will adapt/modify examples that you find online

12 Appendix: Visualizing reactivity with reactlog

  • reactlog can be used to visualize and explore the reactivity of a Shiny app
  • Below we do so for the Shiny app above (app is stored in a folder)
# Restart R to delete log
.rs.restartR()

library(shiny)
library(reactlog)

# tell shiny to log all reactivity
reactlog_enable()
# reactlog_disable()

# run a shiny app
runApp("C:/Users/Paul/Google Drive/13_SHINY_Workshop/shinyapps/guerry/states_paul/app_tab_tabulate.R")

# once app has closed, display reactlog from shiny
shiny::reactlogShow()

13 Appendix: Imperative vs. Declarative programming and laziness

  • Imperative vs. declarative programming (Chapter 3.3.1)
    • Imperative code: “Make me a sandwich” (“assertive” code)
    • Declarative code: “Ensure there is a sandwich in the refrigerator whenever I look inside of it” (“passive-aggressive” code)
    • Shiny follows the latter principles
  • Laziness as strength of declarative programming (Chapter 3.3.2)
    • app will only ever do the minimal amount of work needed to update the output controls that you can currently see

References

Wickham, Hadley. 2021. Mastering Shiny. " O’Reilly Media, Inc.".