Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request: allow deleting an input that was removed using removeUI() #2374

Open
daattali opened this issue Apr 9, 2019 · 11 comments
Open

Request: allow deleting an input that was removed using removeUI() #2374

daattali opened this issue Apr 9, 2019 · 11 comments

Comments

@daattali
Copy link
Contributor

daattali commented Apr 9, 2019

Currently, when an input is removed using removeUI(), its UI is deleted from the DOM but the input still lives from the server's perspective. It would be great if it was possible to clear that input completely.

Personal opinion: I would want this to be the default behaviour, because an input that does not exist on the page should not exist in the server - it's a bit of a logical discrepancy - but that breaking change might cause a lot of trouble so perhaps adding a parameter to removeUI() to do this would be helpful.

@daattali daattali changed the title Request: allow deleting an input that as removed using removeUI() Request: allow deleting an input that was removed using removeUI() Apr 22, 2019
@daattali
Copy link
Contributor Author

For anyone looking for a workaround until this gets implemented, my solution meanwhile it to set the value of any removed inputs to NULL using javascript, with Shiny.onInputChange("input", 'null')

@ColinFay
Copy link
Contributor

ColinFay commented Aug 29, 2019

Here's a reprex of this issue:

library(shiny)
ui <- fluidPage(
  actionButton("rmv", "Remove UI"),
  textInput("txt", "This is no longer useful", "plop")
)

# Server logic
server <- function(input, output, session) {
  observeEvent(input$rmv, {
    removeUI(
      selector = "#txt"
    )
  })
  observe({
    invalidateLater(1000)
    print(input$txt)
  })
}

shinyApp(ui, server)

I definitely second the idea of removing the value from the input list if the input is not there anymore.

The removeUI() function which can remove any part of the UI has to be kept, but there could be a removeInput() function, to be more specific to target the inputs.

@iqis
Copy link

iqis commented Jul 21, 2020

Just dropping a thought here. If you're managing the lifecycle of modules using objects from a bespoke class system, for example, R6, you can register your takedown code as the finalizer function of the module object. The finalizer function will run as the object is garbage collected.

@changwn
Copy link

changwn commented Aug 11, 2021

For anyone looking for a workaround until this gets implemented, my solution meanwhile it to set the value of any removed inputs to NULL using javascript, with Shiny.onInputChange("input", 'null')

Thank you so much for solution. Is this way to update all variable in 'input' as null ? Could I just update one variable as null? Thanks in advance.

@Azureuse
Copy link

Azureuse commented May 3, 2022

@ColinFay thank you for the clear example. I'm surprised at how few examples I can find of removing the input / server data along with the UI.

I've taken the example above and built out a working example that shows a method of removing both the UI and the server elements. My approach wraps the textInput into a module as this is typically the scenario I am in when I use removeUI - I want to remove all the UI elements for a module and its data from wherever I am storing the data.

In the below module_values <- reactiveVal(list()) holds the data for each module, I can't use a reactiveValues() as elements can't be removed from such a 'list'. The work is all done by the add_module function which adds the module's data into module_values and (if required) sets up an observeEvent which will tear down the UI for the module and remove its entry from module_values.

Very happy to take advice on the approach. Hopefully this helps others in the future!

library(shiny)
library(purrr)

## Text Module ----

textUI <- function(id) {
  div(
    id = NS(id, "ui"),
    actionButton(NS(id, "remove"), "Remove UI"),
    textInput(NS(id, "text"), "This is no longer useful", "plop")
  )
}

textServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    list(
      text = reactive(input$text),
      delete = reactive(input$remove)
    )
  })
}


## Main App ----

# utility to hide away the mess of updating the reactiveVal(list())
update_values <- function(values, name, value) {
  vals <- values()
  vals[[name]] <- value
  values(vals)
}

add_module <- function(values, name, server, delete_hook = NULL, remove_selector = NULL) {
  # add module server's return to values list
  update_values(values, name, server)
  
  # if module has a reactive we should monitor for deleting, do so
  if (!is.null(delete_hook)) {
    observeEvent(
      server[[delete_hook]](), {
        removeUI(selector = remove_selector)  # remove the ui
        update_values(values, name, NULL)  # remove the server from our values list
      },
      once = TRUE
    )
  }
}

ui <- fluidPage(
  actionButton("addModule", "Add Module"),
  verbatimTextOutput("wordsOutput")
)

server <- function(input, output, session){
  # can't use reactiveValues as can't remove items from them
  module_values <- reactiveVal(list())
  
  observeEvent(input$addModule, {
    id <- paste("module", input$addModule, sep = "-")
    insertUI(selector = "#addModule", where = "afterEnd", ui = textUI(id))
    add_module(
      module_values,
      name = id,
      server = textServer(id),
      delete_hook = "delete",
      remove_selector = paste0("#", NS(id, "ui"))
    )
  })
  
  words <- reactive({ 
    map(module_values(), ~.x$text())
  })
  
  output$wordsOutput <- renderPrint(words())
}

shinyApp(ui, server)

@iqis
Copy link

iqis commented May 10, 2022

@ColinFay thank you for the clear example. I'm surprised at how few examples I can find of removing the input / server data along with the UI.

I've taken the example above and built out a working example that shows a method of removing both the UI and the server elements. My approach wraps the textInput into a module as this is typically the scenario I am in when I use removeUI - I want to remove all the UI elements for a module and its data from wherever I am storing the data.

In the below module_values <- reactiveVal(list()) holds the data for each module, I can't use a reactiveValues() as elements can't be removed from such a 'list'. The work is all done by the add_module function which adds the module's data into module_values and (if required) sets up an observeEvent which will tear down the UI for the module and remove its entry from module_values.

Very happy to take advice on the approach. Hopefully this helps others in the future!

library(shiny)
library(purrr)

## Text Module ----

textUI <- function(id) {
  div(
    id = NS(id, "ui"),
    actionButton(NS(id, "remove"), "Remove UI"),
    textInput(NS(id, "text"), "This is no longer useful", "plop")
  )
}

textServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    list(
      text = reactive(input$text),
      delete = reactive(input$remove)
    )
  })
}


## Main App ----

# utility to hide away the mess of updating the reactiveVal(list())
update_values <- function(values, name, value) {
  vals <- values()
  vals[[name]] <- value
  values(vals)
}

add_module <- function(values, name, server, delete_hook = NULL, remove_selector = NULL) {
  # add module server's return to values list
  update_values(values, name, server)
  
  # if module has a reactive we should monitor for deleting, do so
  if (!is.null(delete_hook)) {
    observeEvent(
      server[[delete_hook]](), {
        removeUI(selector = remove_selector)  # remove the ui
        update_values(values, name, NULL)  # remove the server from our values list
      },
      once = TRUE
    )
  }
}

ui <- fluidPage(
  actionButton("addModule", "Add Module"),
  verbatimTextOutput("wordsOutput")
)

server <- function(input, output, session){
  # can't use reactiveValues as can't remove items from them
  module_values <- reactiveVal(list())
  
  observeEvent(input$addModule, {
    id <- paste("module", input$addModule, sep = "-")
    insertUI(selector = "#addModule", where = "afterEnd", ui = textUI(id))
    add_module(
      module_values,
      name = id,
      server = textServer(id),
      delete_hook = "delete",
      remove_selector = paste0("#", NS(id, "ui"))
    )
  })
  
  words <- reactive({ 
    map(module_values(), ~.x$text())
  })
  
  output$wordsOutput <- renderPrint(words())
}

shinyApp(ui, server)

I guess this only works with reactive value as a middle layer whilst the input is still there?

@aeynaud
Copy link

aeynaud commented Jun 23, 2022

Have we got any updates regarding this issue? It would be indeed quite useful to have a removeInput() function, instead of having to find some clunky workaround.

@aeynaud
Copy link

aeynaud commented Jun 23, 2022

Hi everyone, I have been struggling for a few days now regarding this issue.

A user can create a cage (for storing animals) in my app. The cage has an ID given by the user, say - 22818. This ID will be used to generate all following dependencies dynamically (buttons to open cage, modify, add animals etc..) each of the inputs created will contain "22818" (modify22818, addAnimal22818 etc..).
Where this becomes an issue is when the user deletes cage 22818 and re-creates the same cage in the same session. All dependencies are re-created with the same id and this causes problems and crashes the app. I could potentially make it so that every input is truly unique, but I am not sure how to keep track of them and make sure they interact with the proper cage.

When deleting that cage, destroying all inputs that come up from a grep of pattern "22818" would solve the issue, but I am aware that there could be better options.

I am open to any suggestions/workaround, thank you very much in advance.

@jcheng5
Copy link
Member

jcheng5 commented Jun 23, 2022

I wouldn’t use the real ID to make those input IDs unique, I’d make them unique using a random value that has no relation to anything.

Back to the larger issue in the thread, it seems like there are related features of 1) removal of input values from the input object (by client, by server, or both); 2) destruction of a group of observers/outputs all at once.

@anirbanshaw24
Copy link

anirbanshaw24 commented Nov 16, 2022

Just assign the moduleServer call to a variable:

all_reactive_values[[ns("my_module-server")]] <-
    moduleServer(id, function(input, output, session) {

Then remove server with
all_reactive_values[[ns("my_module-server")]]$destroy()

Along with the removeUI functions and remove_inputs shown above, this should remove the module completely, right ?

Context: I am creating dynamic modules and updating modules by calling the moduleServer function repeatedly. So each observeEvent were triggered n number of times the moduleServer was called.
It was imperative to clear out the existing old moduleServers and inputs before calling the moduleServer again.
I only update the moduleServer, not the moduleUI. The UI remains same and not removed or updated.

@asbates
Copy link

asbates commented Jun 2, 2023

I wouldn’t use the real ID to make those input IDs unique, I’d make them unique using a random value that has no relation to anything.

Back to the larger issue in the thread, it seems like there are related features of 1) removal of input values from the input object (by client, by server, or both); 2) destruction of a group of observers/outputs all at once.

I think item 2 is needed as well. It would be very helpful to have a removeUI analog removeServer that would destroy any inputs, observers, and outputs. That way when a module is removed the server elements can be removed in addition to client elements.

For modules that are inserted and removed based on user input, developers could follow a pattern like

# when module is added
insertUI(my_module_ui('id'))
my_module_server('id')

# when removed
removeUI('id')
removeServer('id')

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

No branches or pull requests

10 participants