Why is renderUI not working in custom modal?

I have a shiny app with a modal that is displayed when I click on a button. I couldn't use shiny::modalDialog() so I adapted this example from W3schools.

Here's what it looks like:

library(shiny)
library(shinyjs)

ui <- fluidPage(
  tags$head(
    tags$link(rel="stylesheet", href="https://www.w3schools.com/w3css/4/w3.css")
  ),
  useShinyjs(), 
  actionButton("modal", "open modal"),
  div(
    id = "my_modal",
    class = "w3-modal",
    div(
      class = "w3-modal-content w3-animate-zoom",
      div(
        class = "w3-container w3-padding",
        actionButton("generate_ui", "generate UI"),
        uiOutput("new_ui"),
        div(
          class="w3-container w3-padding",
          tags$button(class="w3-button w3-right w3-white w3-border",
                      onclick="document.getElementById('my_modal').style.display='none'",
                      "Close")
        )
      )
    )
  ),
  
  tags$script(
    HTML( '// Get the modal
var modal = document.getElementById("my_modal");

// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
  if (event.target == modal) {
    modal.style.display = "none";
  }
}')
  )
)


server <- function(input, output) {
  
  shinyjs::onclick(
    "modal",
    shinyjs::runjs(
      "document.getElementById('my_modal').style.display='block'"
    )
  )
  
  observeEvent(input$generate_ui, {
    print("hello from modal")
    output$new_ui <- renderUI({
      tagList(
        p("This is the new UI")
      )
    })
  })
  
}

shinyApp(ui = ui, server = server)

In the modal, I have a button that is supposed to render some text. However, when I click on the button, nothing is rendered. This is not a problem with observeEvent() because the statement in print() is correctly displayed in the RStudio console.

Why is renderUI() not working in this situation?

Also asked on StackOverflow

Solved here by Le Zhang:

This is because render events are controlled by Javascripts in Shiny. By default, if the dynamic rendering UI is hidden, in your case is the uiOutput element, Shiny would not render it unless it is visible. The term visible here is more than just toggle the css display state to be block. It needs to hear instructions from javascript listeners. In this case, it explicitly listens to a "shown" type of listener. So the way to solve it is in addition to change the display, you also need to trigger the shown event.

Method 1, just use {shinyjs} package, the easiest way

shinyjs::onclick(
    "modal", 
    shinyjs::show("my_modal")
)

Method 2, use jquery, which is what behind {shinyjs}

shinyjs::onclick(
    "modal", 
    shinyjs::runjs("$('#my_modal').show(); $('#new_ui').trigger('shown');")
)

Method 3, use native js, in case you do not want to use jquery

shinyjs::onclick(
    "modal", 
    shinyjs::runjs(
        "
        document.getElementById('my_modal').style.display = 'block';
        document.getElementById('new_ui').dispatchEvent(new Event('shown', {namespace: '', bubbles: true}));
        "
    )
)

Full code

library(shiny)
library(shinyjs)

ui <- fluidPage(
    tags$head(
        tags$link(rel="stylesheet", href="https://www.w3schools.com/w3css/4/w3.css")
    ),
    useShinyjs(), 
    actionButton("modal", "open modal"),
    div(
        id = "my_modal",
        class = "w3-modal",
        div(
            class = "w3-modal-content w3-animate-zoom",
            div(
                class = "w3-container w3-padding",
                actionButton("generate_ui", "generate UI"),
                uiOutput("new_ui"),
                div(
                    class="w3-container w3-padding",
                    tags$button(class="w3-button w3-right w3-white w3-border",
                                onclick="document.getElementById('my_modal').style.display='none'",
                                "Close")
                )
            )
        )
    ),
    
    tags$script(
        HTML( '// Get the modal
var modal = document.getElementById("my_modal");

// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
  if (event.target == modal) {
    modal.style.display = "none";
  }
}')
    )
)


server <- function(input, output) {
    
    shinyjs::onclick(
        "modal", 
        # shinyjs::runjs(
        # "
        # document.getElementById('my_modal').style.display = 'block';
        # document.getElementById('new_ui').dispatchEvent(new Event('shown', {namespace: '', bubbles: true}));
        # "
        # )
        # 
        # shinyjs::runjs("$('#my_modal').show(); $('#new_ui').trigger('shown');")
        shinyjs::show("my_modal")
    )
    
    observeEvent(input$generate_ui, {
        print("hello from modal")
        output$new_ui <- renderUI({
            tagList(
                p("This is the new UI")
            )
        })
    })
    
}

shinyApp(ui = ui, server = server)

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.