Hey everyone,
I've been debugging something that's driving me crazy, and I think I've found either a bug or at least some unexpected behavior in Shiny/htmlwidgets. Would love to hear your thoughts!
The Issue:
I have a uiOutput that conditionally shows one of two things based on which button you click:
- A
DT::datatablewith data, OR - An error message (just a div with some text)
The datatable uses htmlwidgets::onRender to run some JavaScript. Here's the weird part: if that JavaScript has an error in it, Shiny won't update the UI at all - even when the app logic says it should be showing the error message instead of the table!
What I Expected:
When I click the error button, I should see the error message div (no table).
What Actually Happens:
The R console correctly logs that it's rendering the error message, but the browser still shows the old table. The JavaScript error (in code that shouldn't even be running!) breaks the entire UI update.
Here's a minimal example that shows the problem:
library(shiny)
library(DT)
ui <- fluidPage(
"Click buttons in this order: ",
tags$ul(
tags$li("Table"),
tags$li("Error (works)"),
tags$li("Table"),
tags$li("Error with JS bug (wrongly shows a table instead of an error view)"),
tags$li("Error (works)"),
tags$li("Error with JS bug (now it works)"),
),
actionButton("show_table", "Show Table"),
actionButton("show_error", "Show Error Message (works)"),
actionButton("show_error_js", "Show Error Message (with JS bug)"),
hr(),
h3("UI-Output 'Content'"),
uiOutput("content")
)
server <- function(input, output, session) {
view <- reactiveVal("table")
observeEvent(input$show_table, {
view("table")
})
observeEvent(input$show_error, {
view("error")
})
observeEvent(input$show_error_js, {
view("error_js")
})
output$content <- renderUI({
cat("Rendering:", view(), "\n")
if (view() == "table") {
DT::DTOutput("my_table")
} else {
div(
h2("ERROR MESSAGE", style = "color: red;"),
p("This is what you should see when clicking error buttons"),
p(paste("Current view:", view()))
)
}
})
output$my_table <- DT::renderDT({
DT::datatable(data.frame(view = view(), someData = rnorm(5))) |> htmlwidgets::onRender(htmlwidgets::JS(
switch(view(),
"table" = "function(el, x) { console.log('Table rendered OK'); }",
"error" = "function(el, x) { console.log('Error view - no JS bug'); }",
"error_js" = "function(el, x) { thisWillCauseAnError; }" # JS error
)
))
})
}
shinyApp(ui, server)
In this picture you can see the log of the R session claiming the error view was rendered despite displaying the table:
My Question:
Is this expected behavior? It seems problematic that:
- An output that's not included in the rendered UI still executes
- A JS error in that output silently breaks the UI update
- There's no error message - just stale UI
Should I file this as an issue on GitHub, or am I missing something about how this is supposed to work?
Thanks!
PS: I used AI to improve my writing style, I hope you do not mind. Nevermind, all questions are thought out by me and I am curious to learn more about whether you have had problems like that before and how to prevent things like that.
