5

I want to insert a non predefined number of graph inside my Shiny App. I use a for loop and a series of insertUI.

Thing is when I run it, the text elements behave as expected, but the graphs are all rendering the same image. How can I prevent that?

Here's a reprex:

library(shiny)

ui <- fluidPage(
  tags$div(
    class = "this", 
    actionButton("go", "go")
  )
)

server <- function(input, output, session) {

  observeEvent( input$go , {

    x <- reactiveValues(x = list(iris, mtcars, airquality))

    for (i in 1:3){
      insertUI(
        ".this", 
        ui =  tagList(
          p(paste("Number", i)),
          renderPlot({
            plot(x$x[[i]])
          })
        ))
    }
  })

}

shinyApp(ui, server)

shinyui

Colin FAY
  • 4,849
  • 1
  • 12
  • 29
  • I dont know R or shiny, but it seems to me that the value of the list of reactive values is getting copied all the same inside the x variable on every loop iteration or the result of renderPlot is being overwriten. Try to assign the render plot to a variable or function output directly related to i and use that. valToPlot <- x[[i]] (....) renderPlot(x$valToPlot) – Bruno Oliveira Jan 23 '19 at 12:31

2 Answers2

9

Beware closures in for loops ;). There's no block scope in R, so each for loop iteration shares the same iterator variable i. And the renderXX functions essentially store expressions that aren't evaluated immediately, but only later when it's time to render.

So by the time the plots are ready to render, the for loop is done, i is now 3, and each plot(x$x[[i]]) expression is called as plot(x$x[[3]]).

You can get around this by creating a new scope for each loop iteration using local() or a function. My favorite solution is using lapply as you've found, to run each loop iteration in a function with i as a function-scoped variable.

Many languages without block scope have this same gotcha, like Python and JS:

greg L
  • 4,034
  • 1
  • 19
  • 18
  • 2
    Clearest answer I found on this subject. For loop fails because execution is lazy, and all reactive expressions share the same `i` (no block scope!), not its value. Thanks for referencing Python and JS too. – Guilherme Salomé Sep 10 '21 at 21:34
5

So, found the answer to my own question — using lapply() makes this work:

library(shiny)

ui <- fluidPage(
  tags$div(
    class = "this", 
    actionButton("go", "go")
  )
)

server <- function(input, output, session) {

  observeEvent( input$go , {

    x <- reactiveValues(x = list(iris, mtcars, airquality))

    lapply(1:3, function(i){
      insertUI(
        ".this", 
        ui =  tagList(
          p(paste("Number", i)),
          renderPlot({
            plot(x$x[[i]])
          })
        ))
    })
  })

}

shinyApp(ui, server)
Colin FAY
  • 4,849
  • 1
  • 12
  • 29