34 ๐ R6
As your Shiny application grows in complexity, organizing code in a modular and object-oriented way becomes crucial. While Shiny is inherently reactive and functional, R6 classes provide a powerful tool for encapsulating state, behavior, and logic โ particularly useful in large-scale apps or when designing reusable components inside an R package.
In this chapter, weโll explore:
- What R6 is and why itโs useful in Shiny
- How to define and use R6 classes in a Shiny app-package
- Best practices for integrating R6 with reactivity
- A practical example within a package structure
34.1 What is R6?
R6 is an object-oriented system in R that enables the creation of reference-based objects. Unlike S3 or S4 objects, R6 objects maintain state and support encapsulation, inheritance, and private/public members โ much like objects in other OOP languages (e.g., Python, Java).
This makes R6 especially useful for:
- Managing mutable state (e.g., counters, user sessions)
- Bundling business logic with data
- Reusing logic across modules or apps
In an app packaged as an R package, R6 classes help you separate concerns and avoid global state.
34.1.1 Defining an R6 Class
You define R6 classes using the R6
package. Hereโs a simple example of an R6 class for a counter:
# In R/counter.R
<- R6::R6Class(
Counter "Counter",
public = list(
value = 0,
initialize = function(start = 0) {
$value <- start
self
},increment = function() {
$value <- self$value + 1
self
},reset = function() {
$value <- 0
self
},get = function() {
$value
self
}
) )
Place this class definition inside your R/
directory so it is loaded with the rest of your package code.
34.1.2 Using R6 in the Shiny Server Function
To use the class in your app, instantiate it inside the server
function and wrap its methods in reactive
or observe
blocks where needed:
# In inst/app/server.R
<- function(input, output, session) {
server <- Counter$new()
counter
observeEvent(input$increment_btn, {
$increment()
counter
})
observeEvent(input$reset_btn, {
$reset()
counter
})
$counter_text <- renderText({
outputpaste("Current count:", counter$get())
}) }
And in the UI:
# In inst/app/ui.R
<- fluidPage(
ui actionButton("increment_btn", "Increment"),
actionButton("reset_btn", "Reset"),
textOutput("counter_text")
)
34.2 Reactivity and R6
R6 objects arenโt inherently reactive. If you want your app to respond to changes in an R6 objectโs state, you need to wrap getters in reactive expressions or use reactiveVal
/reactiveValues
internally.
34.3 Strategy 1: Wrap method calls in render*
functions
This is the simplest method, shown above with renderText()
.
34.4 Strategy 2: Use reactive triggers
You can also use reactiveVal
to track state and trigger UI updates:
<- function(input, output, session) {
server <- Counter$new()
counter <- reactiveVal(0)
trigger
observeEvent(input$increment_btn, {
$increment()
countertrigger(trigger() + 1)
})
$counter_text <- renderText({
outputtrigger()
paste("Current count:", counter$get())
}) }
34.5 Organizing R6 Classes in a Package
To integrate smoothly with your app-package:
- Place class definitions in
R/
with clear, modular file names (e.g.,R/counter.R
) - Use
@export
roxygen tags if you want to make classes available to users - Test classes with
testthat
as you would with functions - Avoid instantiating R6 objects at the package level (e.g., outside of functions); do so inside
server()
or module server functions
34.6 Example: Session-Aware Logger
Suppose we want an R6 class that logs user actions with session tracking:
# In R/logger.R
<- R6::R6Class("Logger",
Logger public = list(
session_id = NULL,
logs = character(),
initialize = function(session_id) {
$session_id <- session_id
self
},
log = function(message) {
<- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
timestamp <- paste0("[", self$session_id, " @ ", timestamp, "] ", message)
entry $logs <- c(self$logs, entry)
self
},
get_logs = function() {
$logs
self
}
) )
In the server:
<- function(input, output, session) {
server <- Logger$new(session$token)
logger
observeEvent(input$do_something, {
$log("Button clicked")
logger
})
$log_output <- renderPrint({
output$get_logs()
logger
}) }
34.7 Recap
Using R6 in Shiny app-packages gives you a scalable, maintainable approach to organizing business logic and managing state. It shines when:
- You want object-oriented abstractions
- You need mutable objects tied to user sessions
- Youโre building reusable modules within or across apps
While not every Shiny app needs R6, mastering this tool adds a robust pattern to your Shiny developer toolkit โ particularly when packaging your app for production or distribution.
In the next chapter, weโll explore how to use Shiny modules in combination with R6 objects to further encapsulate functionality within your package structure.