Skip to content

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

Description

@slupczynskim

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority: HighHigh-confidence regression or severe bug affecting many users or current release work.ai-triage:needs-reviewNeeds human review before the workflow takes further action.bugSomething isn't workingneeds-triagewrong locationTarget repository or upstream location and reason.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions