使用“observeEvent”、“updateTabsetPanel”和嵌套模块的意外输出

Unexpected output using `observeEvent`, `updateTabsetPanel` and nested modules

我的应用程序有多个屏幕,我使用 tabsetPanel() 处理它,隐藏选项卡 headers(我 让它们在此处可见以进行调试)并使用 updateTabsetPanel()

选择它们

它在主屏幕上启动(编码为 mod_home_ui() / mod_home_server()) 你按下一个按钮来触发一个动作,会有几个但我只留下一个在这里,叫做“学习”(编码为 mod_learn_ui() / mod_learn_server()

“学习”模块本身包含游戏,这里我只留下两个游戏,为了简单起见,两者使用相同的模块功能。 一个反应值panel_flag,决定应该玩哪个游戏,这里我强制它为FALSE,这意味着应该玩game2。

这最后一步没有像我预期的那样工作,而消息显示代码通过了正确的 updateTabsetPanel() 调用,没有选择预期的选项卡,而且没有选择预期的文本显示在屏幕顶部。

这看起来像是一个命名空间问题,但我不明白我在这里做错了什么。

可以将下面的代码一次性复制粘贴到 运行 应用程序中,这是将发生的情况的 gif 动图:

# main ui and server

app_ui <- function() {
  tagList(
    fluidPage(
      title = "My app",
      tabsetPanel(
        id = "switcher",
        #type = "hidden",
        selected = "home",
        tabPanel("home",  mod_home_ui("home_ui")),
        tabPanel("learn", mod_learn_ui("learn_ui"))
      )
    )
  )
}

app_server <- function(input, output,session) {
  learn <- callModule(mod_home_server, "home_ui")
  observeEvent(learn(), {
    message("In app_server: observeEvent on learn() to switch to 'learn' panel")
    updateTabsetPanel(session, "switcher", selected = "learn")
  })
  callModule(mod_learn_server, "home_ui", learn = learn)
}
# home module

mod_home_ui <- function(id){
  ns <- NS(id)
  tagList(
    textOutput(ns("some_text")),
    actionButton(ns("learn"), "learn")
  )
}

mod_home_server <- function(input, output, session){
  output$some_text <- renderText("I expect clicking on the above to trigger game2, not game1")
  ns <- session$ns
  reactive({
    res <- req(input$learn)
    message(
      'In mod_home_server: returning req(input$learn) in mod_home_server to trigger learn()')
    res
  })
}
# learn module

mod_learn_ui <- function(id){
  ns <- NS(id)
  tabsetPanel(
    id = ns("switcher"),
    #type = "hidden",
    tabPanel("game1", mod_game_ui(ns("game1_ui"))),
    tabPanel("game2", mod_game_ui(ns("game2_ui")))
  )
}

mod_learn_server <- function(input, output, session, learn){
  ns <- session$ns
  
  panel_flag <- eventReactive(learn(), {
    message('In mod_learn_server: eventReactive on learn() to trigger relevant game')
    # in reality this would be computed or random
    FALSE
  })
  
  observeEvent(panel_flag(), {
    message('In mod_learn_server: observeEvent on panel_flag()')
    if (panel_flag()) {
      message('In mod_learn_server:  select "game1" panel')
      updateTabsetPanel(session, "switcher", selected = "game1")
    } else {
      message('In mod_learn_server: select "game2" panel')
      updateTabsetPanel(session, "switcher", selected = "game2")
    }
  })  
  callModule(mod_game_server, "game1_ui")
  callModule(mod_game_server, "game2_ui")
}
# game module

mod_game_ui <- function(id){
  ns <- NS(id)
  tagList(
    textOutput(ns("some_text")),
    "I expect another line of text above this one"
  )
}

mod_game_server <- function(input, output, session){
  ns <- session$ns
  output$some_text <- renderText("I expect this to be shown")
}
library(shiny)
shinyApp(app_ui, app_server)

callModule(mod_learn_server, "learn_ui", learn = learn)

而不是

callModule(mod_learn_server, "home_ui", learn = learn)

应该修复它。

为了确保这种情况不再发生,我制作了一个包来测试闪亮代码的一致性,它的设计考虑了 {golem} 框架和约定。

安装 remotes::install_github("moodymudskipper/shinycheck")

这是我在我的真实应用程序上 运行 shinycheck::check_shiny() 时得到的结果(与上面的略有不同):

shinycheck::check_shiny()
-----------------------------------------------------------------------
Check that all module scripts contain exactly 2 functions named appropriately
-----------------------------------------------------------------------
Check that all module ui functions use ns() or NS() on argument named id/inputId/outputId
-----------------------------------------------------------------------
Check that in ui, module ui functions, named `mod_MODULE_ui` refer to modules which exist and ids fed to them are prefixed with "MODULE_"
-----------------------------------------------------------------------
Check that ns() and NS() are never called in an argument that isn't id/inputId/outputId
-----------------------------------------------------------------------
Check that the module args of callModule are of the form "mod_MODULENAME_server", that there is an R file properly named for "MODULENAME", and that the id argument is prefixed by "MODULENAME_"
* In 'mod_main_server', a call to `callModule` has a module argument `mod_learn_server` and an `id` argument 'home_ui' that is not prefixed by the module name 'learn'
-----------------------------------------------------------------------
Check that modules and module ids mentionned on both ui and server side are consistent
* In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'

我们发现:

In 'mod_main_server', a call to callModule has a module argument mod_learn_server and an id argument 'home_ui' that is not prefixed by the module name 'learn'

In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'

这将使调试变得微不足道。

https://github.com/moodymudskipper/shinycheck

查看更多