R Shiny 模块不会在同一事件中响应式更新

Posted

技术标签:

【中文标题】R Shiny 模块不会在同一事件中响应式更新【英文标题】:R Shiny module not updating reactively within same event 【发布时间】:2019-12-15 08:15:22 【问题描述】:

在 R 中使用模块时,我遇到了反应性问题。如果我更新一个模块,然后尝试用这些更新的值更新另一个模块,我会在更新之前获取这些值。 >

我已经编写了一些基本代码来说明我的意思。在这里,我有一个应用程序更新 rHandsontableOutput 放置在名为 my_module 的模块中,然后在按下按钮时将此更新后的 rHandsontableOutput 复制到名为 module_to_update 的第二个模块中。

我发现my_module 中的第一个表将更新,但module_to_update 中的表不会更新。相反,module_to_update 表将收到my_module 的初始表的副本更新之前。如果我再次按下更新按钮,一切都会按预期进行。

我猜这是我如何处理会话或一般反应值的问题,但我没有想法。

问题:如何设置响应式值和模块,以便我可以在同一个函数调用中对更新的模块数据运行操作? (例如,请参阅下面的 observeEvent(input$update_btn, ...) 调用示例)

图片:

application.R

library(shiny)
library(rhandsontable)

source('my_modules.R')

active_tab = ""

ui <- navbarPage("Module Test Tool",

             tabsetPanel(id = 'mainTabset',
                         tabPanel("My Tab",

                                  #This is the HoT that works as expected, updating when called upon
                                  h4("Table 1"),
                                  myModuleUI('my_module'),
                                  #This is the HoT that does NOT work as expected. This HoT fails to use the updated values from 'my_module' HoT
                                  h4("Table to be updated"),
                                  myModuleUI('module_to_update'),
                                  br(),
                                  br(),

                                  fluidRow(
                                    #this button updates tables to new values
                                    actionButton("update_btn", "Update and Add Tables"),
                                    br(),
                                    br(),
                                    textOutput('table1_sum'),
                                    textOutput('table2_sum'),
                                    br(),
                                    br()
                                  )
                         )
             )

)


server <- function(input, output, session) 

  #Link logic for tab module
  callModule(myModule, 'my_module')



  #This button sums up all the rHandsonTable data frames
  observeEvent(input$update_btn, 


    #Update values in table and integer drop down list before doing basic operations on them
    #New values should be all 5s
    five_col = rep(5,3)
    callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
                                                          col2 = five_col,
                                                          col3 = five_col))



    #Grabs updated module table and does operations on it
    module_data = callModule(getMyModuleData, 'my_module')
    module_int= module_data$module_int
    module_df = module_data$module_df

    output$table1_sum = renderText(
      paste0("Sum of Table 1 is: ", sum(module_df())," | The selected integer is: ", module_int())
    )

    #------------------------------------------------------
    #------------------ERROR BELOW-------------------------
    #------------------------------------------------------
    #THIS IS THE CODE THAT FAILS. This updates a 2nd module that should mirror the updated values. However, this results in old values.
    callModule(updateModule, 'module_to_update', module_int(), module_df())

    #Tries to call on new, updated table
    updated_module_data = callModule(getMyModuleData, 'module_to_update')
    updated_module_int= updated_module_data$module_int
    updated_module_df = updated_module_data$module_df

    #Display results of basic operations on new table
    output$table2_sum = renderText(
      paste0("Sum of Updated Table is: ", sum(updated_module_df())," | The selected integer is: ", updated_module_int())
    )
  )




## Create Shiny app ----
shinyApp(ui, server)

my_modules.R



#Simple module containing one rHandsontable and a drop down list of integers
myModuleUI <- function(id,tab_name)

  ns <- NS(id)

  fluidRow(
    rHandsontableOutput(ns("module_hot")),
    selectInput(ns('module_int_list'),"Integers:",c(1:5), selected = 1)
  )




#Initializes myModuleUI rHandsonTable with some values
myModule <- function(input, output, session) 

  one_col = rep.int('VALUE AT INITIALIZATION',3)
  df = data.frame(col1 = one_col,
                  col2 = one_col,
                  col3 = one_col)

  output$module_hot <- renderRHandsontable(
    rhandsontable(df, stretchH = "none", rowHeaders = NULL)
  )


#Returns myModule data for use outside of the module
getMyModuleData <- function(input,output,session)

  return (
      list(
        module_df = reactive(hot_to_r(input$module_hot)),
        module_int =  reactive(input$module_int_list)
    )
  )


updateModule<- function(input,output,session, new_integer, new_dataframe)
  if(!is.null(new_dataframe))
  
    output$module_hot <- renderRHandsontable(
      rhandsontable(new_dataframe, stretchH = "none", rowHeaders = NULL)
    )
  

  outputOptions(output, "module_hot", suspendWhenHidden = FALSE)

  updateSelectInput(session, "module_int_list",selected = new_integer)



【问题讨论】:

为什么将 rhandsontable 渲染和数据评估分成多个模块?似乎把事情复杂化了很多。你能提供一个最小的例子来指出你的问题吗?很难遵循您的代码结构 您好 Thomas,感谢您的反馈。我可能会进一步剥离它。不过,为了给你上下文,这是我实际问题的一个非常简化的版本。我的工具具有模块化的选项卡面板,其中包含 rhandsontables。我正在执行一个更新一个模块中的 rhandsontable 的操作,然后在一个单独的模块中使用更新的值。这是我试图用简单的术语重现问题。 【参考方案1】:

这里有几个问题...

您正在调用具有相同命名空间的多个不同模块。模块应该彼此独立运行。他们每个人都应该有自己的命名空间。以下不正确:

callModule(myModule, 'my_module') 

callModule(updateModule, 'my_module', 5, data.frame(col1 = five_col,
                                                          col2 = five_col,
                                                          col3 = five_col))

module_data = callModule(getMyModuleData, 'my_module')

您正在从observeEvent() 中调用模块。这意味着每次您观察到该事件时,您都会尝试初始化该模块。您不想初始化模块,而是想将新变量传递给该模块。如果你让一个模块返回它的值,然后将这些返回的值用作另一个模块的输入,你不需要观察事件......接收新信息的模块将决定是否观察变化。

您创建了一个函数/模块getMyModuleData,它只应该返回存在于不同模块中的数据。相反,您应该让其他模块返回您想要的数据。

查看:https://shiny.rstudio.com/articles/communicate-bet-modules.html

【讨论】:

感谢 Adam——我在面向对象语言中使用类/对象等模块的方法似乎是错误的。从这个意义上说,就好像我正在尝试使用updateModule“设置”my_module 模块,然后使用getMyModuleData“获取”参数。你说“如果你让一个模块返回它的值,然后使用这些返回的值作为另一个模块的输入,你不需要观察事件......接收新信息的模块将决定是否观察变化。”我通读了那篇文章,但仍然不确定。关于如何在观察之外执行此操作的任何建议? 明白了。在这种情况下,模块应该被视为不同的程序。您通过在启动模块(程序)时为其提供命名空间(唯一 ID)来创建模块(程序)的实例。这使您可以创建同一模块的多个实例(但每个实例都是完全不同的程序)。模块(又名程序)可以通过输入和返回相互通信。主app.r负责对每个模块进行一次初始化,并负责从一个模块中获取返回值并将它们作为输入传递给另一个模块。

以上是关于R Shiny 模块不会在同一事件中响应式更新的主要内容,如果未能解决你的问题,请参考以下文章

「R」Shiny:响应式编程server 函数

「R」Shiny:响应式编程响应式编程

R Shiny DT - 使用反应式编辑表中的值

R Shiny 中的响应式 CSS 属性

如何使用 `renderUI` 响应式更新 Shiny 应用程序中的活动菜单项?

闪亮的响应式 UI 在同一条件变量上挂起多个 uiOutput 调用