Search code examples
rshiny

Why the observer does not get auto destroyed when created and used in a module?


Consider this example where if I dismiss the modal dialog, launch it again and then click the notification button, I get 2 notifications instead of 1. observeEvent has autoDestroy = TRUE then why does it not get destroyed when I dismiss the modal dialog? Is there a parameter in observeEvent that can fix this?

enter image description here

Reprex

library(shiny)
library(bslib)

# Module UI function
myModuleUI <- function(id) {
  # Empty UI
  tagList()
}

# Module server function
myModuleServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    # Show modal when the module server is triggered
    showModal(modalDialog(
      title = "Modal from Module",
      actionButton(NS(id, "modalBtn"), "Click for notification"),
      footer = modalButton("Dismiss"),
      easyClose = TRUE
    ))
    
    # Observer for the modal button
    observeEvent(input$modalBtn, {
      showNotification("Hello from the module!", type = "message")
      removeModal()
    })
  })
}

# Main UI
ui <- page_fluid(
  card(
    card_header("Module Demo"),
    card_body(
      actionButton("triggerModule", "Show Modal")
    )
  ),
  myModuleUI("myModule")
)

# Main server
server <- function(input, output, session) {
  observeEvent(input$triggerModule, {
    myModuleServer("myModule")
  })
}

shinyApp(ui, server)

Solution

  • The problem is that you are creating a new module server every time the trigger button is clicked. Instead of doing that, create it once and pass the input to the server.

    library(shiny)
    library(bslib)
    
    # Module UI function
    myModuleUI <- function(id) {
        # Empty UI
        tagList()
    }
    
    # Module server function
    myModuleServer <- function(id, trigger) {
        moduleServer(id, function(input, output, session) {
            # Show modal when the module server is triggered
            observeEvent(trigger(),{
                showModal(modalDialog(
                    title = "Modal from Module",
                    actionButton(NS(id, "modalBtn"), "Click for notification"),
                    footer = modalButton("Dismiss"),
                    easyClose = TRUE
                ))
            })
            
            # Observer for the modal button
            observeEvent(input$modalBtn, {
                showNotification("Hello from the module!", type = "message")
                removeModal()
            })
        })
    }
    
    # Main UI
    ui <- page_fluid(
        card(
            card_header("Module Demo"),
            card_body(
                actionButton("triggerModule", "Show Modal")
            )
        ),
        myModuleUI("myModule")
    )
    
    # Main server
    server <- function(input, output, session) {
        myModuleServer("myModule", reactive(input$triggerModule))
    }
    
    shinyApp(ui, server)
    

    It seems like when you pass inputs, you need to wrap them in a reactive() call.