-1

To all R Shiny experts: Which of the following three server functions would you rate first, second and third - and why?

I had an intensive discussion today about which of the three solutions comes closest to "best practice" Shiny app design. (While they all three work the same.)

For example, version C seems odd to me since overwriting render functions conditionally is unnecessary (since conditional output rendering is what these functions were made for).

The original app contains much more logic when dealing with input values, of course. I simplified the example to make the differences obvious.

library(shiny)

ui <- fluidPage(

  shiny::radioButtons(
    inputId = "some_input", 
    label = "Please choose:", 
    choices = c("something", "nothing")
  ),

  shiny::textOutput(
    outputId = "some_output"
  )  

)

# version A: all logic within rendering function
server <- function(input, output, session) {

   output$some_output <- shiny::renderText({

       if(input$some_input == "something"){
         # imagine some complex logic here
         "some value was chosen"
       } else {
         NULL
       }

   })

}

# version B: most logic within input observer, 
# using reactive session userData
server <- function(input, output, session) {

  session$userData$memory <- shiny::reactiveValues(
    "stored_value" = NULL
  )

  output$some_output <- shiny::renderText({
    session$userData$memory$stored_value
  })

  shiny::observeEvent({
    input$some_input
  }, {

    if(input$some_input == "something"){
      # imagine some complex logic here
      session$userData$memory$stored_value <- "some value was chosen"
    } else {
      session$userData$memory$stored_value <- NULL
    }
  })

}

# version C: all logic within observer, 
# setting the rendering function conditionally
server <- function(input, output, session) {

  shiny::observeEvent({
    input$some_input
  }, {

    if(input$some_input == "something"){
      # imagine some complex logic here
      output$some_output <- shiny::renderText({ "some value was chosen" })
    } else {
      output$some_output <- shiny::renderText({ NULL })
    }

  })

}

shinyApp(ui = ui, server = server)
nilsole
  • 1,663
  • 2
  • 12
  • 28
  • "Best practices" are just opinions unless you define exactly what you mean by "best" here. Questions that solicit opinions are explicitly off topic on Stack Overflow. Different applications may have different requirements which need different solutions. – MrFlick Nov 12 '19 at 21:16
  • 2
    (1) *"where should I put the complex logic?"* Not directly in the `shiny` app. Write functions (elsewhere) that generally do their work without a shiny interface. (2) *"which ... comes closest"* is very much just an opinion. (3) putting `renderText` inside of `observeEvent` is not going to do what you might think it should. Have you found a way for that code to work? – r2evans Nov 12 '19 at 21:18
  • @r2evans: (3) Yes that code works. – nilsole Nov 12 '19 at 21:23

1 Answers1

1

I am by no means a Shiny expert but as "best" isn't defined, I figured I would give my opinion based on the apps I've created (no documentation provided to support).

Order from best to worst:

  1. A
  2. B
  3. C

Reasoning:

C: although having output$some_output in multiple places works, it is never good practice to do this and just creates confusion in the code

B: the observeEvent is repetitive as the renderText() is designed to observe when a reactive variable gets changed. I know you have a more complicated app but storing to reactiveValues in this example is over the top without gaining any benefit.

A: Very simple, basic code that works seamlessly. You could also argue you could take the if statement out of the renderText() and wrap it in a reactive() to keep it cleaner but they accomplish the same thing.

I'd be curious if anyone would do time studies or has some actual documentation to back up a "best" to "worst".

Kevin
  • 1,974
  • 1
  • 19
  • 51