Выражение Shiny WatchEvent выполняется более одного раза - PullRequest
0 голосов
/ 02 апреля 2019

При нажатии кнопки действия один раз моё выражение наблюдаем в два раза.

В частности, при выполнении приведенного ниже кода, если нажать кнопку «Добавить элемент», а затем нажать первую кнопку «Удалить», сообщение «удаленный 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)

Почему оператор print выполняется более одного раза, когда кнопка удаления нажимается только один раз? Как это можно исправить?

Изначально у меня не было ни observer$destroy(), ни once = TRUE. Они были добавлены каждый в попытке остановить запуск кода несколько раз.

Мои версии пакета:

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

Ответы [ 2 ]

1 голос
/ 02 апреля 2019

Это потому, что новый наблюдатель создается для всех существующих кнопок удаления при нажатии Add Item. Это можно исправить, отследив, какая кнопка была нажата, и создав наблюдателя только для новой созданной кнопки. Я уверен, что это может быть использовано в приведенном выше примере, однако лично мне было немного трудно следовать с использованием splat и mapply. В любом случае, добавление новой кнопки может быть упрощено с помощью 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)
0 голосов
/ 02 апреля 2019

Спасибо Sada93 за ответ, который очень хорошо объяснил проблему.Данное решение работает, но включает в себя много изменений, поэтому я хотел посмотреть, есть ли более простой способ.Похоже, что сделать идентификаторы уникальными - это один из способов ее решения.Делая идентификатор уникальным с отметкой времени, он предотвращает добавление наблюдателя дважды, потому что элемент в основном перестраивается.Возможно, это не самое эффективное решение, но оно работает.

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)
...