0
votes

I came across a situation where a reactive expression is evaluated multiple times when it calls some inputs within renderUI widgets.

Please check the following simple code.

library(shiny)


server <- function(input, output) {
  output$INPUT_1 = renderUI({
    selectInput("input_1","Input 1",choices = letters)  
  })
  output$INPUT_2 = renderUI({
    selectInput("input_2","Input 2",choices = letters)  
  })
  output$INPUT_3 = renderUI({
    selectInput("input_3","Input 3",choices = letters)    
  })
  output$INPUT_4 = renderUI({
    selectInput("input_4","Input 4",choices = letters)    
  })


  output$text = renderText({
    print("1")
    paste(input$input_1,input$input_2,input$input_3,input$input_4)
  })  

}

ui <- fluidPage(
  uiOutput("INPUT_1"),
  uiOutput("INPUT_2"),
  uiOutput("INPUT_3"),
  uiOutput("INPUT_4"),
  textOutput("text")
)

shinyApp(ui = ui, server = server)

If you take a look at your console, you'll see

Listening on http://127.0.0.1:4939 
[1] "1" 
[1] "1"

The "1" came twice.

My own app has the "1" coming more than 3 times. Since each of my reactive deals with big data, this kind of scenario really embarrasses my users and myself.

I'm almost sure that this has something to do with the renderUI facility. But I still cannot find a way to fix it. I considered using updateXXXXX features but my input UIs contain very complex calculations. So using updateXXXXX is something I am trying to avoid.

How can the my reactive expression be evaluated only once?

1
A reactive expression is evaluated every time a (linked) ui element changes. So server is evaluated when the app loads and all inputs are empty. Than the reactive UI elements are populated and your print is evaluated a second time. (add if( length(input$input_1) > 0) print("1") to the output$text as an example)Arcoutte

1 Answers

2
votes

Shiny comes with an extra function called req that helps you control the execution of observers even though some control elements are rendered on the fly within some server sided call.

See this page here for the full documentation. In short, you can list a set of input variables within a reactive environment (observer, reactive value or render function) and this reactive environment will not be calculated/executed until all the input variables have a value assigned.

In your case, you can just add the line

req(input$input_1, input$input_2,input$input_3, input$input_4)

to your observer and it will only be executed once. (It was executed twice for the reasons stated in Arcoutte's comment to your post.)

Complete sample code below.

library(shiny)

server <- function(input, output) {
  output$INPUT_1 = renderUI({
    selectInput("input_1","Input 1",choices = letters)  
  })
  output$INPUT_2 = renderUI({
    selectInput("input_2","Input 2",choices = letters)  
  })
  output$INPUT_3 = renderUI({
    selectInput("input_3","Input 3",choices = letters)    
  })
  output$INPUT_4 = renderUI({
    selectInput("input_4","Input 4",choices = letters)    
  })


  output$text = renderText({
    req(input$input_1,input$input_2,input$input_3,input$input_4)

    print("1")
    paste(input$input_1,input$input_2,input$input_3,input$input_4)
  })  

}

ui <- fluidPage(
  uiOutput("INPUT_1"),
  uiOutput("INPUT_2"),
  uiOutput("INPUT_3"),
  uiOutput("INPUT_4"),
  textOutput("text")
)

shinyApp(ui = ui, server = server)