n <- 1
result <- 1 + n
n <- 2
result <- 1 + nShiny reactivity
Learn about reactivity
Key idea: specify a graph of dependencies, so that when an input changes, all related output are updated automatically.
Usual way in R: procedural. You tell what to do and when. Imperative programming
Reactive programming: define how to do something; when the right condition meets. Declarative programming
result <- reactive({
1 + input$n
})Server function
This is how a skeleton of shiny app looks like:
library(shiny)
ui <- fluidPage(
# front end interface
)
server <- function(input, output, session) {
# back end logic
}
shinyApp(ui, server)In the UI part, every user gets the same UI. Not every user gets the same server: user A moves a slider doesn’t affect user B. Each session has a unique state, isolating the variables created inside the function. Almost all the reactive programming are inside the server function.
Reactivity
ui <- fluidPage(
textInput("name", "What's your name?"),
textOutput("greeting")
)
server <- function(input, output, session) {
output$greeting <- renderText({
paste0("Hello ", input$name, "!")
})
}Every time the input$name changes, the output also changes. The reactivity simply means that every time a user updates the browser input, the developer does not need to re-run the program. The output is automatically updated by itself.
My understanding: the developer only needs to develop the reactive code, then leaves everything to the user. The program reacts to the input
Order of execution
Reactive graph: describes how inputs and outputs are connected.
Reactive context
Reactive values can only be used inside reactive contexts. Access reactive values outside reactive context will lead to an error. E.g.
server <- function(input, output){
print(input$num)
}
# this results in an error, as 'print' is not a reactive context- Any
render*()is a reactive context - Use
observe({...})access reactive variable: it is a reactive context
# this is the correct way to print
server <- function(input, output){
observe({
print(input$num) # put in the reactive context
})
}Reactive expressions / variables
Can create a reactive variable using reactive({}), which is a reactive context.
The order of these two lines below doesn’t matter.
server <- function(input, output, session) {
# define a reactive expression here
string <- reactive(paste0("Hello ", input$name, "!"))
# can not simply do the line below:
# paste0("Hello ", input$name, "!")
# call it
output$greeting <- renderText(string())
}Access custom reactive variables like a function: need the (). For example, call string() rather than string.
server <- function(input, output){
# create a reactive variable
x <- reactive({
input$num + 1
})
observe({
print(input$num)
print(x()) # with ()
})
}Observe, observeEvent
shiny::observe creates observer. It updates input’s value. Does not have a return value. It gets executed when the reactive values (e.g. input$value) it depends on changes.
Common use: updateSelectInput, updateSliderInput, updateTextInput to change the input value.
# example from custom module
shiny::observe({
shiny::updateSelectInput(
session,
"dataset",
choices = names(data())
)
})This observe block runs every time input data changes.
shiny::observeEvent is a special case of observe. It only focuses on one specific change. That is why it has an additional argument, eventExpr, which specifies the event to listen to.
# specify what to watch
observeEvent(input$dataset, {
req(input$dataset)
numeric_vars <- names(data()[[input$dataset]])[sapply(data()[[input$dataset]], is.numeric)]
shiny::updateSelectInput(
session,
"variable",
choices = numeric_vars)
})NS and ns
Namespace. NS is the function, ns is the object created by NS(id).
NStakes one argument:id. It returns a function (a namespacing function).ns <- NS(id)is the standard first line of a UI module function.nsfunction remembers the ID you assigned.
ns <- shiny::NS(namespace = '1')
ns('my_button') # returns "1-my_button"[1] "1-my_button"
The purpose of having NS and namespace is to avoid ID collisions when you use the same module multiple times in a single app.
ui_module_ui <- function(id) {
# 1. Create the namespacing function `ns`
ns <- shiny::NS(id)
# 2. Use `ns` to wrap every ID
tagList(
actionButton(ns("my_button"), "Click me"),
plotOutput(ns("my_plot"))
)
}ui_module_ui("1")will return1-my_button,1-my_plotui_module_ui('2')will return2-my_button,2-my_plot