Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shiny: insertUI with an id previously removed with removeUI

I have one code in which I add and remove objects by using insertUI and removeUI as it can be seen below:

library(shiny)

# Define the UI
ui <- fluidPage(
  actionButton("adder", "Add"),
  tags$div(id = 'placeholder')
)


# Define the server code
server <- function(input, output) {
  rv <- reactiveValues()

  rv$counter <- 0

  observeEvent(input$adder,{
    rv$counter <- rv$counter + 1
    add <- sprintf("%03d",rv$counter)

    filterId <- paste0('adder_', add)
    divId <- paste0('adder_div_', add)
    elementFilterId <- paste0('adder_object_', add)
    removeFilterId <- paste0('remover_', add)

    insertUI(
      selector = '#placeholder',
      ui = tags$div(
        id = divId,
        actionButton(removeFilterId, label = "Remove filter", style = "float: right;"),
        textInput(elementFilterId, label = "Introduce text", value = "")
      )
    )

    # Observer that removes a filter
    observeEvent(input[[removeFilterId]],{
      rv$counter <- rv$counter - 1
      removeUI(selector = paste0("#", divId))
    })
  })
}

# Return a Shiny app object
shinyApp(ui = ui, server = server, options = list(launch.browser = T))

The problem that I'm experiencing is that if I add one UI (click on Add), then remove it (click on Remove filter) and then add a new one (click on Add again), the first time I click on it, doesn't work.

I know that it's due to the fact that I'm using an ID I've used previously but, theoretically, I've completely removed it with the removeUI instruction.

What am I missing in here?

like image 770
asuka Avatar asked Sep 07 '25 14:09

asuka


2 Answers

FYI there is a way to clear the Server of input IDS so you don't run into the same problem. I found the solution here.

https://roh.engineering/post/shiny-add-removing-modules-dynamically/

The article goes into a lot of detail, but you can use the following function to remove inputs from the server.

remove_shiny_inputs <- function(id, .input) {
  invisible(
    lapply(grep(id, names(.input), value = TRUE), function(i) {
      .subset2(.input, "impl")$.values$remove(i)
    })
  )
}
like image 66
Jordan Webb Avatar answered Sep 09 '25 02:09

Jordan Webb


Though I consider that there should be a more elegant way of solving this problem, I have found a workaround that solves it.

In summary, I've added to the ID of the object that I create a random string that I've created; thus, the id will never be repeated.

library(shiny)

generateRandomString <- function(n = 10, m = 10) {
  elements <- c()

  chars <- c(LETTERS, letters)

  for(idx in 1:n) {
    element <- c()

    for(entry in 1:m) {
      val <- sample(c("pair","odd"),1)

      switch(val,
        pair = {
          # Add a letter
          element <- c(element, sample(chars,1))
        },
        odd = {
          # Add a number
          element <- c(element, as.character(sample(0:9,1)))
        }
      )
    }

    elements <- c(elements, paste0(element,collapse=""))
  }

  elements
}

# Define the UI
ui <- fluidPage(
  actionButton("adder", "Add"),
  tags$div(id = 'placeholder')
)


# Define the server code
server <- function(input, output) {
  rv <- reactiveValues()

  rv$counter <- 0

  observeEvent(input$adder,{
    rv$counter <- rv$counter + 1

    add <- sprintf("%03d",rv$counter)

    prefix <- generateRandomString(1,20)
    filterId <- paste0(prefix,'_adder_', add)
    divId <- paste0(prefix,'_adder_div_', add)
    elementFilterId <- paste0(prefix,'_adder_object_', add)
    removeFilterId <- paste0(prefix,'_remover_', add)

    insertUI(
      selector = '#placeholder',
      ui = tags$div(
        id = divId,
        actionButton(removeFilterId, label = "Remove filter", style = "float: right;"),
        textInput(elementFilterId, label = "Introduce text", value = "")
      )
    )

    # Observer that removes a filter
    observeEvent(input[[removeFilterId]],{
      rv$counter <- rv$counter - 1
      removeUI(selector = paste0("#", divId))
    })
  })
}

# Return a Shiny app object
shinyApp(ui = ui, server = server, options = list(launch.browser = T))

There are better ways of creating such a random string but the point is that the issue is solved.

like image 23
asuka Avatar answered Sep 09 '25 02:09

asuka