posit-dev / posit-dev/py-shiny

[Bug]: Uncaught TypeError in _shinyResizeObserver.ts when dynamic components/modals are removed

Open
#2,273 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

ai-triage:needs-review bug needs-triage Priority: High wrong location
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:

  1. The DOM elements are detached/unbound, which clears their associated jQuery data (shinyOutputBinding).
  2. The browser triggers a final ResizeObserver callback on the resizing/disappearing container.
  3. The callback runs query selectors inside the target for .shiny-bound-output and finds the detached elements.
  4. Because the elements are detached/unbound, calling $(el).data("shinyOutputBinding") returns undefined.
  5. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.