Rhino uses box
to ensure reusable and modular code, both from using packages and using the scripts within the app itself. Rhino favors the explicit over the implicit, and a local scope over a global scope.
Sometimes we need global definitions however and storing constants is the most common reason for that.
The suggested way to create global constants in Rhino is to define and export the constant inside its own R
script in app/logic
.
# app/logic/constant.R #' @export answer <- 42
The global constant can be imported and used in other modules.
# app/logic/another_module.R box::use(app/logic/constant[answer])
Most of the time, it is sufficient to define a constant and then export and use it throughout the app.
However, there are instances when an object may need to change its value as the app runs. In such cases, Rhino suggests the following ways to handle global variables.
Note: Using global variables can make the app more difficult to manage and understand. If the variable can change its value at any point in the app, future changes to the app must consider how this global variable will be affected. A changing global variable may also make testing difficult because tests may affect the global variables. The tests are no longer independent of each other.
In R
, global variables live inside .GlobalEnv
. Global variables can be updated within a function using <<-
.
# constants.R answer <- 42 set_answer <- function(new_answer) { answer <<- new_answer }
# main.R source("constants.R") print(answer) # 42 set_answer(0) print(answer) # 0
When code is loaded with box::use()
, global variables live inside the module's own immutable environment. Updating global variables with <<-
will not work.
# app/logic/constants.R #' @export answer <- 42 #' @export set_answer <- function(new_answer) { answer <<- new_answer }
# app/main.R box::use(app/logic/constants) print(constants$answer) # 42 constants$set_answer(0) # Error: cannot change value of locked binding.
To overcome box
's feature of limiting scope, Rhino suggests creating a new environment and use that environment to contain the global variables.
# app/logic/__init__.R #' @export global <- new.env() global$answer <- 42 #' @export set_answer <- function(new_answer) { global$answer <- new_answer }
# app/logic/get_answer.R box::use( app/logic[global, set_answer], ) print(global$answer) # 42 set_answer(0) print(global$answer) # 0
.GlobalEnv
Alternatively, variables can still be stored in and imported from .GlobalEnv
. The variable must also be defined and updated using <-
.
# app/logic/__init__.R .GlobalEnv$answer <- 42 #' @export set_answer <- function(new_answer) { .GlobalEnv$answer <- new_answer }
# app/logic/get_answer.R box::use(app/logic[set_answer]) print(.GlobalEnv$answer) # 42 set_answer(0) print(.GlobalEnv$answer) # 0
Rhino suggests using arguments in module servers for explicit handling of session variables or user inputs.
module_ui <- function(id) { ns <- NS(id) textOutput(ns("answer")) } module_server <- function(id, answer) { moduleServer(id, function(input, output, session) { output$answer <- renderText(answer()) }) } shinyApp( ui = bootstrapPage( textInput("answer", "Answer"), module_ui("module") ), server = function(input, output, session) { answer <- reactive(input$answer) module_server("module", answer) } )
However, shiny
has support for session$userData
, an environment that can store session-specific data.
module_ui <- function(id) { ns <- NS(id) textOutput(ns("answer")) } module_server <- function(id) { moduleServer(id, function(input, output, session) { output$answer <- renderText(session$userData$answer()) }) } shinyApp( ui = bootstrapPage( textInput("answer", "Answer"), module_ui("module") ), server = function(input, output, session) { session$userData$answer <- reactive(input$answer) module_server("module") } )
All modules have access to the variables inside session$userData
.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.