#6343·nicegui

Element stays registered when a constructor raises after `Element.__init__`

Author: falkoschindlerCreated Sep 16, 2026Updated Sep 17, 2026
Labelsbuganalysis

Description

Element.__init__ commits a new instance to client.elements, appends it to the parent slot and enqueues it in the outbox. If any constructor frame above it raises afterwards, nothing is rolled back: the half-built element stays in the tree, gets rendered, and shows up in every later traversal (ElementFilter, User.should_see, event dispatch through client.elements).

We have fixed four instances by hand, each time by moving the raising statement in front of super().__init__() and leaving a NOTE about the ordering: #6271 (mixins/cancelable_wait_element.py), #6282 (codemirror/line_anchors.py), #6323 (sub_pages.py) and #6330 (select.py). Those were the cases where the leftover element was also missing attributes, so any page traversal crashed with an AttributeError.

Two more cases are still open. They do not crash, but they leave a ghost element on the page:

  • ui.html('<script>...</script>') raises ValueError from _handle_content_change, which runs inside the content setter after registration.
  • ui.image(Path('missing.png')) (and every other SourceElement) raises FileNotFoundError from _set_props, which runs inside the source setter after registration.

Both checks live in setters that are shared with set_content() / set_source(), so the "validate before super()" pattern would mean duplicating them. Per-element reordering also cannot cover a raise inside an inherited constructor downstream of the registration, or user-defined subclasses that raise after super().__init__().

Proposal

Handle it once in Element instead: if construction fails after registration, unregister the element again (remove from client.elements, the parent slot's children and the outbox), including any children the failed constructor already created. This cannot reuse delete() / Client.remove_elements(), because _handle_delete() overrides assume a fully built element. With a general fix in place the four NOTEs and the ordering constraints can go.

Minimal example

python
from nicegui import ui


@ui.page('/')
def page():
    try:
        ui.html('<script>alert(1)</script>')
    except ValueError:
        pass
    ui.label('page still works')


ui.run()

The page renders an empty Html element in front of the label, and ElementFilter(kind=ui.html) finds it.