4

This is an old Shiny issue: shiny app will not work if the same "output" is used two times in Ui.R

A simple example :

library(shiny)
## app.R ##
server <- function(input, output) {
  output$distPlot <- renderPlot({
    hist(rnorm(input$obs), col = 'darkgray', border = 'white')
  })
}

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100)
    ),
    mainPanel(plotOutput("distPlot")
              # If the next line is commented out, histogram appears correctly
              ,plotOutput("distPlot")
              )
  )
)

shinyApp(ui = ui, server = server)

This doesn't work because :

Shiny doesn't support multiple outputs with the same name. This code would generate HTML where two elements have the same ID, which is invalid HTML. See this or this.

The result looks fine, but isn't what is expected (no histogram shown) :
enter image description here

The ShinyApp seems to be working normally :

Listening on http ://127.0.0.1:7081

Although I know this issue, I have already been tricked a few times in complex UIs, and I was wondering if there was a way to output an automatic warning in the console on this?
For example :

Warning message: Output 'distPlot' is used twice in UI - this is not supported and might lead to unexpected results

Thanks for sharing your solutions on this issue!

Waldi
  • 39,242
  • 6
  • 30
  • 78

2 Answers2

2

Under the assumption that most Shiny UI outputs follow this pattern :

outputTypeOutput("outputname")

I wrote the checkShinyOutput function which can be called before UI definition in UI script :

checkShinyOutput <- function(){
  tryCatch({
      parsed <- getParseData(parse(file = rstudioapi::getSourceEditorContext()$path))
      shinyOutput <- parsed[parsed$token=='SYMBOL_FUNCTION_CALL'& grepl("^[a-z]+[A-z]+Output$",parsed$text),]
      shinyOutput <- merge(shinyOutput,parsed,by='line1')
      shinyOutput <- shinyOutput[shinyOutput$token.y == "STR_CONST",]
      warn <- table(shinyOutput$text.y)
      warn <- warn[warn>=2]
      warnname <- names(warn)
      if (length(warn>1)) {
       warning(mapply(function(warn,nb){paste("Output",warn,"is used",nb,"times")},warnname,warn))
      }
    },
    error = function(){},
    warning = function(cond) {
      message("Shiny UI : check following warnings to avoid unexpected UI behaviour")
      message(cond)
    } 
  )
} 

This appears in Console when the app is run from RStudio :

> runApp('test')
Shiny UI : check following warnings to avoid unexpected UI behaviour
Output "distPlot" is used 2 times
Listening on http://127.0.0.1:7414
Waldi
  • 39,242
  • 6
  • 30
  • 78
  • Feedback in my answer. – thothal Jul 06 '20 at 09:36
  • Thanks for your feedback, I was aware that it would only cover basic cases, but this is up to now 100% of my shiny use ;-). But agree with you, when there's a problem, just have a look at the js console. – Waldi Jul 06 '20 at 10:02
1

I think that it may be quite some task to print it out to the R console. But if you open your app and go to the JS console you should see an error message:

Duplicate


Feedback as requested (here in the answer to allow for formatting purposes):

Your code covers basic cases, but there are some edge cases which you may be overlooking (this list does not claim to be exhaustive):

  1. If the name of the output element is in the next line, your code will not detect it.
  2. If the name of the element is not a string but in a variable it won't work either.

(and this are just 2 cases I can quickly think of - and they are not completely unrealistic to be honest)

Overall, I think it will be quite some work to cover all cases, while a quick look into the JS console reveals all you need, just in a different environment. IMHO completeness beats convenience.

outname <- "distPlot"
ui <- fluidPage(
   sidebarLayout(
      sidebarPanel(
         sliderInput("obs", "Number of observations:", min = 10, max = 500, value = 100)
      ),
      mainPanel(plotOutput("distPlot"),
                plotOutput(
                   "distPlot" # not covered b/c in other line
                ),
                plotOutput(outname) # not covered b/c in variable
      )
   )
)
thothal
  • 16,690
  • 3
  • 36
  • 71
  • thanks for your answer, I didn't think about this but it really makes sense. I'll wait a while to accept it because a warning in the console would still be more visible. – Waldi Jul 02 '20 at 05:53
  • I was toying with the idea to create an error handler on the JavaScript side and informing R via `Shiny.setInputValue`, however this approach would not work because apparently the error in [Shiny javascript](https://github.com/rstudio/shiny/blob/e7ec5e5ba4915a4bf57ac8f21166ea916b44e379/srcjs/shinyapp.js#L339) prevents the `Shiny` javascript objetc to be properly instantiated, so we cnanot go through this route. – thothal Jul 02 '20 at 08:52
  • interested in feedback on the answer I posted – Waldi Jul 04 '20 at 07:17