Skip to content

Conversation

gadenbuie
Copy link
Member

@gadenbuie gadenbuie commented Sep 23, 2025

For #2374

This is a bit of proof-of-concept, but isn't far from something we could ship. The idea is to add a $remove() method to ReactiveValues and a session$removeInputs() that uses this method to remove a key from a reactive values list. Some notes:

  1. Removing an input only removes it from the list of inputs, without invalidating its value. This means that downstream observers that use this value directly aren't updated unless by another method. This preserves the current behavior, but if a future update is performed (because another reactive value is invalidated), reading the removed input has the same behavior as reading an un-initialized input.

  2. On the other hand, we do invalidate names(input) and reactiveValuesToList(input), since these have effectively changed due to the removal of the input.

  3. session$removeInputs() removes the input from the map on the server side. It does not change behavior on the client. We may need to revisit this and potentially remove any bindings to the input ID. I think this might be a good idea to ensure that if you add a new input UI with the same ID as a previous, but server-side removed input, it will be treated as a brand-new input. An alternative would be to make sure that UI added via insertUI() and renderUI() always report input values when bound (as opposed to only reporting changed values if the initial value is different from the last value before it was removed).

Here's a small app that shows all of the above behavior:

library(shiny)

ui <- fluidPage(
  actionButton("toggle", "Toggle Input UI"),
  div(
    id = "removable-input",
    textInput("txt", "This is no longer useful", "beep"),
  ),
  textInput("txt2", "Some other text", "boop"),
  p(
    "As of",
    textOutput("ui_time", inline = TRUE),
    "the first inputs says",
    uiOutput("ui_txt", inline = TRUE),
    "and the second input says",
    uiOutput("ui_txt2", inline = TRUE)
  ),
  actionButton("update", "Update text output"),
  h3("Names"),
  verbatimTextOutput("names"),
  h3("Values"),
  verbatimTextOutput("values")
)

# Server logic
server <- function(input, output, session) {
  observeEvent(input$toggle, {
    if (input$toggle %% 2 == 0) {
      insertUI(
        selector = "#removable-input",
        ui = textInput("txt", "This is no longer useful", "beep")
      )
    } else {
      removeUI(
        selector = "#removable-input .form-group"
      )
      session$removeInputs("txt")
    }
  })

  output$ui_time <- renderText({
    input$update
    strftime(Sys.time(), "%F %T,")
  })

  output$ui_txt <- renderText({
    input$update
    HTML(sprintf("<code>%s</code>", input$txt))
  })

  output$ui_txt2 <- renderText({
    input$update
    HTML(sprintf("<code>%s</code>.", input$txt2))
  })

  output$names <- renderPrint({
    names(input)
  })

  output$values <- renderPrint({
    reactiveValuesToList(input)
  })
}

shinyApp(ui, server)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant