简体   繁体   中英

How to force evaluation in shiny render when generating dynamic number of elements?

I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input. I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput , and I have an observe that will feed them with the content using renderValueBox .

My problem is: the code in the renderValueBox , for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop .

Here is a min reprex:

library(shiny)
library(shinydashboard)
library(shinyWidgets)

# Function

compute <- function(id)
{
  print(paste("Compute ", id))
  return(id)
}

# UI

ui = shinyUI(fluidPage(
  
  titlePanel("Compare"),
  useShinydashboard(),
  
  sidebarLayout(
    sidebarPanel(
      numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
    ),
    mainPanel(
      uiOutput("boxes")
    )
  )
))

# Server

server = shinyServer(function(input, output, session) {
  
  data <- reactiveValues(
    ids = list()
  )
  
  output$boxes <- renderUI({
    print("boxes")
    box_list <- list()
    id_list <- list()
    for(id in 1:(input$numitems)) {
      id_box  <- paste0("box_", id)
      print(paste("boxes - ", id_box))
      id_list <- append(id_list, id_box)
      box_list <- append(
        box_list,
        tagList(
          shinydashboard::valueBoxOutput(id_box)
        )
      )
      data$ids <- id_list
    }
    print("boxes end")
    fluidRow(box_list)
  })
  
  observe({
    print("observe")
    for(id_box in data$ids) {
      print(paste("observe - ", id_box))
      output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
    }
    print("end observe")
  })
  
  
})

# Run

shinyApp(ui = ui , server = server)

Here is the result:

代表的输出

And the console output:

控制台的输出

As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.

I tried using force , computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.

Do anyone know a way to force execution during the loop? Or see another way of achieving the same result?

Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.

Anyway, one way to correct this ( found here ) is to encapsulate the render inside the local function, like this:

observe({
    print("observe")
    for(id_box in data$ids) {
      print(paste("observe - ", id_box))
      local({
        tmp <- id_box
        output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
      })
    }
    print("end observe")
  })

Now the compute is still called after the end of the observe , but the tmp variable has the correct value:

日志控制台

The result is what I wanted:

闪亮的结果

For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM