posit-dev / posit-dev/py-shiny
[Bug]: Uncaught TypeError in _shinyResizeObserver.ts when dynamic components/modals are removed
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.8k
- Forks
- 135
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 21
Description
Component
UI (ui.*)
Severity
P2 - Medium (workaround exists)
Shiny Version
1.6.1+
Python Version
3.12
Minimal Reproducible Example
from shiny import App, render, ui
import matplotlib.pyplot as plt
app_ui = ui.page_fluid(
ui.input_action_button("open_modal", "Open Modal with Plot"),
)
def server(input, output, session):
@render.plot
def my_plot():
fig, ax = plt.subplots()
ax.plot([1, 2], [3, 4])
return fig
@input.open_modal
def _():
ui.modal_show(
ui.modal(
ui.card(
ui.card_header("Card inside Modal"),
ui.output_plot("my_plot"),
),
title="Reproduction Modal",
easy_close=True
)
)
app = App(app_ui, server)
Behavior
When a container that is registered with the ShinyResizeObserver (such as a card, sidebar, or a layout component inside a modal) is removed from the DOM, the observer does not automatically stop watching it.
During the cleanup/removal transition:
- The DOM elements are detached/unbound, which clears their associated jQuery data (
shinyOutputBinding). - The browser triggers a final
ResizeObservercallback on the resizing/disappearing container. - The callback runs query selectors inside the target for
.shiny-bound-outputand finds the detached elements. - Because the elements are detached/unbound, calling
$(el).data("shinyOutputBinding")returns undefined. - Destructuring binding and onResize from undefined throws an uncaught JavaScript error, halting the client-side execution.
Proposed Solution
It seems like the underlying issue is actually in bslib.
A defensive check or fallback should be added in _shinyResizeObserver.ts to safely ignore elements whose bindings have already been torn down or are not yet set:
const outputData = $(el).data("shinyOutputBinding");
if (!outputData) return;
const { binding, onResize } = outputData;
if (!binding || !binding.resize) return;
Or using a fallback:
const { binding, onResize } = $(el).data("shinyOutputBinding") || {};
if (!binding || !binding.resize) return;
Disclaimer: This Bug Report was prepared with the help of Antigravity and manually verified.
I greatly appreciate all the hard work the maintainers have poured into the shiny ecosystem and would not like to add AI slop here 😅
Error Messages (if any)
Uncaught TypeError: Cannot destructure property 'binding' of '$(...).data(...)' as it is undefined.
at _shinyResizeObserver.ts:81:21
at NodeList.forEach (<anonymous>)
at ResizeObserver.<anonymous> (_shinyResizeObserver.ts:78:12)
Environment
Python package: shiny (version 1.6.0 / 1.6.1+)
Underlying UI package: bslib (frontend assets compiled from rstudio/bslib version 0.10.0 / 0.11.0+)
OS: Win10
Browser: Edge 149.0.4022.69
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the minimal Python reproduction and inspect _shinyResizeObserver.ts around line 81, where the ResizeObserver callback reads shinyOutputBinding data. Reproduce opening and removing the modal, then verify that cleanup no longer produces an uncaught TypeError when detached outputs are encountered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100