Search code examples
rshinyshinymodules

Passing additional parameters to moduleServer in R Shiny app: accessing parent environment for updateTabsetPanel


  • As of Shiny 1.5.0, we are encouraged to use moduleServer rather than callModule.
  • However, it appears as though we can't pass additional parameters to moduleServer (unlike callModule).
  • This is an issue because I'd like to pass the parent session as a parameter to moduleServer, so that I can correctly reference the parent session in order for updateTabsetPanel to work correctly inside a renderUI dynamic output.

This post demonstrates how to use callModule to pass the parent session into the module so that updateTabsetPanel can reference the parent session and update it accordingly. I used that approach to create a minimal example here. There are two tabPanels, and we are trying to navigate to the second via an actionLink in the first. The first_server module is called with callModule, and we are able to pass the parent session parameter as an additional argument, allowing us to reference it in the updateTabsetPanel call in first_server. Clicking the actionLink then takes us to the second module UI, as expected:

library(shiny)

first_ui <- function(id) {
    ns <- NS(id)
    uiOutput(ns("goto_second_link_ui"))
}

second_ui <- function(id) {
    ns <- NS(id)
    p("It worked!")
}

first_server <- function(input, output, session, parent) {
    output$goto_second_link_ui <- renderUI({
        actionLink(session$ns("goto_second"), label = "Go to second module")
    })
    observeEvent(input$goto_second, {
        updateTabsetPanel(parent,
            inputId = "main",
            selected = "second"
        )
    })
}

ui <- {
    fluidPage(
        tabsetPanel(id = "main",
            tabPanel(value = "first",
                title = "First module",
                first_ui("first")    
            ),
            tabPanel(value = "second",
                title = "Second module",
                second_ui("second")    
            )
        )
    )
}

server <- function(input, output, session) {
    callModule(first_server, id = "first", parent = session)
}

shinyApp(ui, server)

I've tried a couple of approaches to get this to work with moduleServer. The best guess I had was to pass the parent session all the way through to the module argument of moduleServer (relevant changes only shown):

first_server <- function(id, parent) {
    moduleServer(id, function(input, output, session, parent = parent) {
        output$goto_second_link_ui <- renderUI({
            actionLink(session$ns("goto_second"), label = "Go to second module")
        })
        observeEvent(input$goto_second, {
            updateTabsetPanel(parent,
                inputId = "main",
                selected = "second"
            )
        })
    })
}

server <- function(input, output, session) {
    first_server("first", parent = session)
}

This generates an error when I click the actionLink that I don't fully understand:

Warning: Error in updateTabsetPanel: promise already under evaluation: recursive default argument reference or earlier problems?

Thanks for reading and any suggestions.


Solution

  • Once you remove the extra argument in moduleServer it works. Try this

    library(shiny)
    
    first_ui <- function(id) {
      ns <- NS(id)
      uiOutput(ns("goto_second_link_ui"))
    }
    
    second_ui <- function(id) {
      ns <- NS(id)
      p("It worked!")
    }
    
    first_server <- function(id,parent) {
      moduleServer(id, function(input, output, session) {
        ns <- session$ns
        output$goto_second_link_ui <- renderUI({
          actionLink(ns("goto_second"), label = "Go to second module")
        })
        observeEvent(input$goto_second, {
          updateTabsetPanel(parent,
                            inputId = "main",
                            selected = "second"
          )
        })
      })
    }
    
    ui <- {
      fluidPage(
        tabsetPanel(id = "main",
                    tabPanel(value = "first",
                             title = "First module",
                             first_ui("first")    
                    ),
                    tabPanel(value = "second",
                             title = "Second module",
                             second_ui("second")    
                    )
        )
      )
    }
    
    server <- function(input, output, session) {
      first_server("first", parent = session)
    }
    
    shinyApp(ui, server)