Destroying reactive domains


In my app I'm using the following module-calling pattern:

myModule_server <- function(input, output, session){
  callback <- reactiveVal()
  observeEvent(input$addTabButton, {
    # Names are based on currently opened tab count, e.g. tab_(n+1)
    newName <- generateName() 
    # getui produces ui with correctly specified NS based on newName
    tabUI <- tabPanel(getui(newName), title = newName) 
    appendTab('tabset', tabUI, select = TRUE)
    callModule(module = tabModule, id = newName, callback)
  observeEvent(input$removeTabButton, {
    tabValue = getTabValue() #Irrelevant how
    removeTab('tabset', tabValue)
  observeEvent(callback(), {
    # do some stuff with output from any tab (tabModule returns id-value combination)

tabModule <- function(input, output, session, callback){

That is adding new tab to tabset on button click with namespaced UI part, and calling module. Then destroying tab with another button's click.

The issue is that after destroying a tab and making a new one, I'm running callModule with the id previously used before. And, by entering the tabModule, I discovered that states of reactives inside have been preserved from the previous call.

While generating unique IDs with every new tab is not a problem, it bothers me that there are environments left somewhere for each tab module and those nested inside it. Is it possible to clean up reactive domains?

Since the question is theoretical, I thought I'll have a better shot with a short schematic example, sacrificing reproducibility.


The current tab removal implementation is done by only removing the UI elements. This causes the outputs to never trigger (as there is no corresponding input), effectively removing the execution of the code.

Cleaning up the reactives is not as clear of a processes. There could be other modules (created within this module or other places) that reference the same reactive values, making it hard to destroy all internal reactives that a module can see.

The disadvantage is we will typically have a dead part of the reactive dependency graph, but that is ok pending memory (rarely a problem). If memory is tight in general, there is also nothing stopping the user from manually clearing the reactives before calling removeTab(), effectively clearing the memory usage.