闪亮的 observeEvent 表达式运行不止一次

Posted

技术标签:

【中文标题】闪亮的 observeEvent 表达式运行不止一次【英文标题】:Shiny observeEvent expression runs more than once 【发布时间】:2019-08-23 03:46:11 【问题描述】:

单击操作按钮一次时,我的 observeEvent 表达式运行了两次。

具体来说,当运行下面的代码时,如果单击“添加项目”按钮,然后单击第一个“删除”按钮,则会打印两次“已删除 1”消息。这是我最初在更复杂的环境中观察到的行为的一个最小示例。

(在那个更复杂的示例中,多次运行的行为表现为单击一个删除按钮时所有项目都被删除。我确定这是因为删除特定索引处的项目的删除逻辑运行了多次次。)

library(plyr)
library(shiny)

ui <- fluidPage(
  actionButton("addItem", "Add Item"),
  uiOutput("items")
)

server <- function(input, output, session) 
  itemsReactive <- reactiveVal(list(Item1 = "foo"))
  observeEvent(input$addItem, 
    itemsReactive(c(itemsReactive(), list(Item2 = "foo")))
  )
  output$items <- renderUI(
    splat(div)(
      unname(mapply(function(item, index) 
        deleteButtonId <- paste('delete-button', index, sep = '-')
        observer <- observeEvent(input[[deleteButtonId]], 
          print(paste("deleted", index))
          observer$destroy()
        , once = TRUE)
        div(actionButton(deleteButtonId, "Delete"))
      , itemsReactive(), seq_along(itemsReactive()), SIMPLIFY = FALSE))
    )
  )


shinyApp(ui = ui, server = server)

为什么只单击一次删除按钮,打印语句会运行多次?如何解决这个问题?

最初,我没有observer$destroy() 也没有once = TRUE。添加这些是为了阻止代码多次运行。

我的包版本:

other attached packages:
[1] plyr_1.8.4  shiny_1.2.0

【问题讨论】:

【参考方案1】:

这是因为当点击Add Item 时,会为所有现有的删除按钮创建一个新的观察者。这可以通过跟踪已单击的按钮并仅为创建的新按钮创建观察者来解决。我确信这可以用于您上面提供的示例,但是,就个人而言,使用splatmapply 有点难以理解。无论如何,使用tagList 可以简化添加新按钮。

library(shiny)

ui <- fluidPage(
  actionButton("addItem", "Add Item"),
  uiOutput("items")
)



server <- function(input, output, session) 

  new_bttn_added <- reactiveVal(0) #index to track new button
  delete_id <- c() #IDs of the delete buttons
  index <- 1 #Counter
  taglist <- tagList() #Button Taglist to display in uiOutput("items")

  output$items <- renderUI(
    req(input$addItem) # reactivity when addItem is clicked

    delete_id <<- c(delete_id,paste0("bttn",index)) #Append the new ID of the button being created
    taglist <<- tagList(taglist,div(actionButton(delete_id[index],"Delete"))) #Append button to taglist
    index <<- index + 1 #Increment index

    #Increment the button counter
    isolate(
      val <- new_bttn_added()
      val <- val + 1
      new_bttn_added(val)
    )
    return(taglist)
  )

  observe(
    #This section is triggered only when a new button is added
    # Reactive dependance on only new_bttn_added() to avoid race conditions

    id <- delete_id[new_bttn_added()]
    lapply(id,function(x)
      observeEvent(input[[x]],
        # Do something with the new delete button here
        cat("Pressed",x,"\n")
      )
    )
  )


shinyApp(ui = ui, server = server)

【讨论】:

感谢您的洞察力 - 在您的解释之后,我能够找到一种可能更简单的方法来修复重复运行。我已将您的答案标记为已选中,因为这是一种很好的方法,但我在下面添加了另一个解决方案,该解决方案仅涉及删除我认为如果您好奇我会使用的 ID。【参考方案2】:

感谢 Sada93 的回答,因为它很好地解释了问题。给出的解决方案有效,但涉及许多更改,所以我想看看是否有更简单的方法。看起来使 ID 唯一是解决它的一种方法。通过使用时间戳使 ID 唯一,它可以防止观察者被添加两次,因为元素基本上是重建的。这可能不是最有效的解决方案,但确实有效。

curTime <- toString(round(as.numeric(Sys.time()) * 1000))
deleteButtonId <- paste('delete-button', index, curTime, sep = '-')

在上下文中:

library(plyr)
library(shiny)

ui <- fluidPage(
  actionButton("addItem", "Add Item"),
  uiOutput("items")
)

server <- function(input, output, session) 
  itemsReactive <- reactiveVal(list(Item1 = "foo"))
  observeEvent(input$addItem, 
    itemsReactive(c(itemsReactive(), list(Item2 = "foo")))
  )
  output$items <- renderUI(
    splat(div)(
      unname(mapply(function(item, index) 
        curTime <- toString(round(as.numeric(Sys.time()) * 1000))
        deleteButtonId <- paste('delete-button', index, curTime, sep = '-')
        observer <- observeEvent(input[[deleteButtonId]], 
          print(paste("deleted", index))
          observer$destroy()
        , once = TRUE)
        div(actionButton(deleteButtonId, "Delete"))
      , itemsReactive(), seq_along(itemsReactive()), SIMPLIFY = FALSE))
    )
  )


shinyApp(ui = ui, server = server)

【讨论】:

是的,这可行,但在后端,您可能会得到很多未使用的观察者。不确定随着时间的推移,这可能会产生什么样的开销。 我被这个问题困了几个小时。这肯定有很大帮助。谢谢。

以上是关于闪亮的 observeEvent 表达式运行不止一次的主要内容,如果未能解决你的问题,请参考以下文章

闪亮的observeEvent复制输出

闪亮,observeEvent,updateSelectInput,输入重置

在 R 闪亮中,如何使“observeEvent”不会被“updateSelectizeInput”的更改触发

应用启动时闪亮的 observeEvent 触发器

R闪亮:observeEvent和eventReactive的不同行为

使用 purrr::pwalk 从 tibble 创建多个闪亮的 observeEvents