Reputation: 2022
The app below contains a module that inserts a UI object each time the Add
button is clicked. The UI object consists of two inputs:
selectInput
with choices A
and B
. textInput
if the user chooses A
and a
numericInput
if they choose B
.However, when I click Add
, the inserted UI only contains Input 1 (the selectInput
) - Input 2 is not rendered, as shown below:
Whereas the desired output looks like this:
I'm not sure if this is a namespacing issue or if there is a problem in the scoping of the module. Printing the IDs to the console checks out:
The app is as follows:
library(shiny)
# module UI function
modUI <- function(id){
ns <- NS(id)
tagList(
actionButton(ns('add'), 'Add'),
div(id = ns('placeholder'))
)
}
# module server function
modServer <- function(input, output, session) {
ns = session$ns
ctn <- reactiveVal(0)
Id <- reactive({
function(id){
ns(paste0(id, ctn()))
}
})
observeEvent(input$add, {
ctn(ctn() + 1)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = div(
id = Id()('div'),
selectInput(Id()('letter'), 'Letter:', LETTERS[1:2]),
uiOutput(Id()('input'))
)
)
})
observeEvent(ctn(), {
id <- Id()('input')
selection <- Id()('letter')
print(list(id = id, selection = selection))
req(input[[selection]])
output[[id]] <- renderUI({
req(input[[selection]])
switch(
input[[selection]],
'A' = textInput(Id()('text'), 'ENTER TEXT', ''),
'B' = numericInput(Id()('numeric'), 'ENTER NUMBER', '')
)
})
}, ignoreInit = TRUE)
}
# main ui
ui <- fluidPage(
modUI('mod1')
)
# main server
server <- function(input, output, session) {
callModule(modServer, "mod1")
}
# run app
shinyApp(ui, server)
I tried splitting the module up into an inner and outer module. The inner mod creates Input 1 and Input 2 and the outer mod inserts them into the main app using insertUI
. This gives me the same outcome as before though. The code for this can be viewed below:
library(shiny)
# INNER MOD ---------------------------------------------------------------
innermodUI <- function(id) {
ns = NS(id)
tagList(
selectInput(ns('letter'), 'Letter:', LETTERS[1:2]),
uiOutput(ns('names'))
)
}
innermodServer <- function(input, output, session) {
ns = session$ns
output$names <- renderUI({
selection = req(input$letter)
switch(
selection,
'A' = textInput(ns('text'), 'ENTER TEXT', ''),
'B' = numericInput(ns('numeric'), 'ENTER NUMBER', '')
)
})
}
# OUTER MOD ---------------------------------------------------------------
modUI <- function(id){
ns <- NS(id)
tagList(
actionButton(ns('add'), 'Add'),
div(id = ns('placeholder'))
)
}
modServer <- function(input, output, session) {
ns = session$ns
ctn <- reactiveVal(0)
Id <- reactive({
function(id){
ns(paste0(id, ctn()))
}
})
observeEvent(input$add, {
ctn(ctn() + 1)
filterId = Id()('filter')
insertUI(
selector = paste0('#', ns('placeholder')),
ui = innermodUI(filterId)
)
callModule(innermodServer, filterId)
})
}
# MAIN --------------------------------------------------------------------
ui <- fluidPage(
modUI('mod1')
)
server <- function(input, output, session) {
callModule(modServer, "mod1")
}
shinyApp(ui, server)
I also tried wrapping the renderUI
in a shinyjs::delay()
to no avail. I would really appreciate any help on this since I'm not well-versed in Shiny modules and don't know what to try next.
Upvotes: 5
Views: 1656
Reputation: 84519
I've managed to make it work by multiple trials-errors. As I understand (I'm still new in Shiny modules), you have to use session$ns
only for the inputs created in the server.
library(shiny)
# module UI function
modUI <- function(id){
ns <- NS(id)
tagList(
actionButton(ns('add'), 'Add'),
div(id = ns('placeholder'))
)
}
# module server function
modServer <- function(input, output, session) {
ns = session$ns
ctn <- reactiveVal(0)
Id <- reactive({
function(id){
paste0(id, ctn())
}
})
IdNS <- reactive({
function(id){
ns(paste0(id, ctn()))
}
})
observeEvent(input$add, {
ctn(ctn() + 1)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = div(
id = Id()('div'),
selectInput(IdNS()('letter'), 'Letter:', LETTERS[1:2]),
uiOutput(IdNS()('input'))
)
)
})
observeEvent(ctn(), {
id <- Id()('input')
selection <- Id()('letter')
output[[id]] <- renderUI({
switch(
input[[selection]],
'A' = textInput(IdNS()('text'), 'ENTER TEXT', ''),
'B' = numericInput(IdNS()('numeric'), 'ENTER NUMBER', '')
)
})
}, ignoreInit = TRUE)
}
# main ui
ui <- fluidPage(
modUI('mod1')
)
# main server
server <- function(input, output, session) {
callModule(modServer, "mod1")
}
# run app
shinyApp(ui, server)
Upvotes: 5