4

I want to wrap Shiny module in kind of "try catch" to prevent it from crashing in any unexpected way. Module represents one of shinydashboard tabs, so the app is still usable after one of components fails.

I'm aware of possibility of using req function not to stop the application after single error (proposed e.g. in this post) but the goal here is to achieve quick win by reducing user cost of errors for quite complex app with a substantial technical debt.

Expected behaviour: after module crash modal is shown but user can still use the app.

Reproducible example:

library(shiny)

counterButton <- function(id, label = "Counter") {
  ns <- NS(id)
  tagList(
    actionButton(ns("button"), label = label),
    verbatimTextOutput(ns("out"))
  )
}

counter <- function(input, output, session) {
  count <- reactiveVal(0)
  observeEvent(input$button, {
    count(count() + 1)
    stop("Test observeEvent error")
  })
  output$out <- renderText({
    count()
  })
  count
}

ui <- fluidPage(
  counterButton("counter1", "Counter #1")
)

server <- function(input, output, session) {
  tryCatch({
    callModule(counter, "counter1")
  }, error = function(err) {
    showNotification(paste("Error: ", err$message), type = "error")}
  )}

shinyApp(ui, server)

I've found a partial solution (see here) and it indeed works but it involves changes in application modules code, which is not always possible for me to do:

tryObserveEvent <- function(eventExpr, handlerExpr, ...) {
  eventExpr <- substitute(eventExpr)
  handlerExpr <- substitute(handlerExpr)

  env <- parent.frame()

  shiny::observeEvent(tryCatch(
    eval(eventExpr, env),
    error = function(e) {
      showNotification(paste("Error: ", e$message), type = "error")
    }
  ),
  {
    tryCatch(
      eval(handlerExpr, env),
      error = function(e) {
        showNotification(paste("Error: ", e$message), type = "error")
      }
    )
  }, ...)
}

popup

AdamB
  • 788
  • 5
  • 11

1 Answers1

0

Recently I came across the same need. This is a small try for your example. Hope it helps. It helped me, to at least start working on a good solution for my use case- a complex app fully modularised

The idea behind this solution, is to have a reactive Value that monitors if the module has successfully completed its job. The observer in the module is wraped in a tryCatch. I guess I can wrap all reactives in the module with a tryCatch. In case of a reactive or observer fails, then the module is unsuccessful, and the main server function in the app monitors this behavior and informs the user (see then observeEvent(..) in the main server function. Also, I return the success indicator from the module server. See return(mod_success).

library(shiny)

counterButton <- function(id, label = "Counter") {
  ns <- NS(id)
  tagList(
    actionButton(ns("button"), label = label),
    verbatimTextOutput(ns("out"))
  )
}

counter <- function(input, output, session) {
  count <- reactiveVal(0)

  mod_success <- reactiveValues(
    success = TRUE,
    error = NULL
  )
  
  observeEvent(input$button, {
      
    tryCatch(
      expr = {
        count(count() + 1)
        if(count() == 4) {
          stop("You cannot count to: ", count())
        } 
      },
      error = function(errr){
        
        mod_success$success <- FALSE
        mod_success$error <- errr$message
        
      }
    )
    
    
  })
  
  output$out <- renderText({
    
    count()
    
  })
  
  return(mod_success)
  
  
}

ui <- fluidPage(
  
  counterButton("counter1", "Counter #1")
  
)

server <- function(input, output, session) {
  
  res <- callModule(counter, "counter1")
  
  observeEvent(res$success,{
    
    if(isFALSE(res$success)){
      
      showNotification(paste0("Error: ", res$error), type = "error")
      
    }
    
  })
  
  
}

shinyApp(ui, server)
Lefkios Paikousis
  • 462
  • 1
  • 6
  • 12