You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hat tip to @khusmann for clearly articulating this problem.
When dynamic UI is removed (via remove_ui(), @render.ui re-rendering, or toggling visibility), the server-side reactive objects associated with that UI are not cleaned up. This creates three types of dangling state:
Dangling effects -- reactive.effect objects created for dynamic UI keep firing even after the UI is removed from the DOM.
Dangling calcs -- reactive.calc objects persist in memory with stale values. Calc_ has no destroy() method at all.
Dangling input values -- input.foo() still returns the last value for inputs that no longer exist in the DOM.
The todo list example (shiny/api-examples/todo_list/app-core.py:93-97) manually calls effect.destroy() and comments that this is needed because remove_ui only removes HTML.
R Shiny (rstudio/shiny)
This is a long-standing problem in R Shiny as well:
rstudio/shiny#2281 -- The canonical issue: insertUI/callModule to add, removeUI/??? to remove. No mechanism to deactivate a module server instance.
rstudio/shiny#825 -- "Reactive subDomains" proposal (2016). Proposes createSubDomain() where ending the subdomain destroys all reactive objects created within it.
rstudio/shiny#2374 -- Request to delete server-side input values on removeUI().
Users must manually track every reactive.effect and call .destroy() on removal. This doesn't scale, doesn't work for reactive.calc (no destroy method), and doesn't clean up input values.
Summary
Hat tip to @khusmann for clearly articulating this problem.
When dynamic UI is removed (via
remove_ui(),@render.uire-rendering, or toggling visibility), the server-side reactive objects associated with that UI are not cleaned up. This creates three types of dangling state:reactive.effectobjects created for dynamic UI keep firing even after the UI is removed from the DOM.reactive.calcobjects persist in memory with stale values.Calc_has nodestroy()method at all.input.foo()still returns the last value for inputs that no longer exist in the DOM.Reproduction
Open the demo app in Shinylive
reactive.effect, areactive.calc, and a dynamicinput_text.[REMOVED], but the effect count keeps going up and the input/calc values persist.Root Cause
The server has no concept of reactive object ownership or scoping tied to UI lifecycle:
insert_ui()/remove_ui()are purely client-side DOM operations. The server sends a message and forgets about it.Calc_lacks basic lifecycle management -- nodestroy(), nosession.on_ended()callback.What IS cleaned up today
Outputs.remove()callseffect.destroy()Effect_on session endsession.on_ended(self.destroy)shinyUnbindAll(el)before DOM replacement_manage_hidden()suspends (but does NOT destroy)What is NOT cleaned up
reactive.effectfrom module server afterremove_uireactive.calc(nodestroy()method exists)input.*values for removed DOM inputsinsert_ui/remove_uiPrior Art
py-shiny
remove_ui, the associated input value stays the same as the last input. #399 --remove_uiinput values persist. Closed as expected behavior with manual workaround.shiny/api-examples/todo_list/app-core.py:93-97) manually callseffect.destroy()and comments that this is needed becauseremove_uionly removes HTML.R Shiny (rstudio/shiny)
This is a long-standing problem in R Shiny as well:
insertUI/callModuleto add,removeUI/??? to remove. No mechanism to deactivate a module server instance.createSubDomain()where ending the subdomain destroys all reactive objects created within it.removeUI().Current Workaround
Users must manually track every
reactive.effectand call.destroy()on removal. This doesn't scale, doesn't work forreactive.calc(no destroy method), and doesn't clean up input values.Possible Directions (not prescriptive)
destroy()toCalc_remove_ui/@render.uiand server-side cleanup