Search code examples
rshinyshinymodules

problem in adding several shiny modules using insertUI


I build a shiny app that need to add pieces of UI dynamically based on some parameter I'll know only in real time. I created a simplistic reconstruction of my needs, and encountered a problem I describe below

so in my example I have a module called mblock. for the sake of this example it only displays a text. the actual text to display is decided at run time, and so is the number of texts (and hence blocks) will be decided at runtime

for the specific example I set texts to be a fixed vector containing all the texts to be shown, but in reality it will be computed as a reactive object. the code is below:

library(shiny)

#block module
mblockUI = function(id) {
    ns = NS(id)
    fluidRow(
        textOutput(ns("text"))
    )
}

mblock = function(input,output,session,actual_text) {
    output$text = renderText({actual_text})
}


# Define the main ui 
ui <- fluidPage(
    uiOutput("all_blocks"),
    actionButton("submit","submit")
)

# Define server logic
server <- function(input, output) {
    texts = c("aaaa","bbbb","cccc") #this is a sample vector of texts to be shown in blocks

    output$all_blocks = renderUI({
        for(i in 1:length(texts)) {
            mname = paste0("block",i)  #block name to be created (the name of the module)
            #print(mname)
            insertUI("#submit","beforeBegin",mblockUI(mname))  #adding the ui
            #now adding the server side of each block
            #also passing the text to be shown
            callModule(mblock,mname,texts[i])     
        }
    })
}

# Run the application 
shinyApp(ui = ui, server = server)

The problem is that all the blocks show the same text (the last one). and I don't understand why

any ideas how to fix the code? what do I miss (shiny version 1.4.0)


Solution

  • First of all, insertUI is able to work "on its own" and doesn't need renderUI. You can put it in an observe environment instead. However, be careful of the output of insertUI since it is persistent, as explained in the documentation of this function:

    Unlike renderUI(), the UI generated with insertUI() is persistent: once it's created, it stays there until removed by removeUI(). Each new call to insertUI() creates more UI objects, in addition to the ones already there (all independent from one another). To update a part of the UI (ex: an input object), you must use the appropriate render function or a customized reactive function.

    I don't know why but the for loop doesn't work (as your example shows) whereas lapply does (see this answer for example).

    Here's your example with these corrections:

    library(shiny)
    
    #block module
    mblockUI = function(id) {
      ns = NS(id)
      fluidRow(
        textOutput(ns("text"))
      )
    }
    
    mblock = function(input,output,session,actual_text) {
      output$text = renderText({actual_text})
    }
    
    
    # Define the main ui 
    ui <- fluidPage(
      actionButton("submit","submit")
    )
    
    # Define server logic
    server <- function(input, output) {
      texts = c("aaaa","bbbb","cccc") #this is a sample vector of texts to be shown in blocks
      
      observe({
        lapply(1:length(texts), function(i) {
          mname = paste0("block",i)  #block name to be created (the name of the module)
          #print(mname)
          insertUI("#submit","beforeBegin",mblockUI(mname))  #adding the ui
          #now adding the server side of each block
          #also passing the text to be shown
          callModule(mblock,mname,texts[i])     
        })
      })
    }
    
    # Run the application 
    shinyApp(ui = ui, server = server)