yewstack/yew · error

could not remove property

Error message

could not remove property

What it means

To remove a DOM property during diffing, AttributeWriter::remove (packages/yew/src/dom_bundle/btag/attributes.rs:213) 'unsets' it with Reflect::set(el, key, JsValue::UNDEFINED) and unwraps with .expect("could not remove property"). Reflect::set throws a TypeError when the property has no setter (getter-only accessors such as dataset or classList) or is non-writable, so removing such a property panics even though setting it may never have been attempted.

Source

Thrown at packages/yew/src/dom_bundle/btag/attributes.rs:213

                debug_assert_eq!(
                    el.get_attribute(key).as_deref(),
                    Some(value.as_ref()),
                    "attribute `{key}` does not match the server-rendered value during hydration",
                );
            }
            AttributeOrProperty::Property(_) => Self::set(el, key, value),
        }
    }

    fn remove(el: &Element, key: &str, old_value: &AttributeOrProperty) {
        match old_value {
            AttributeOrProperty::Attribute(_) => el
                .remove_attribute(intern(key))
                .expect("could not remove attribute"),
            AttributeOrProperty::Property(_) => {
                let key = JsValue::from_str(key);
                js_sys::Reflect::set(el.as_ref(), &key, &JsValue::UNDEFINED)
                    .expect("could not remove property");
            }
        }
    }
}

impl Apply for Attributes {
    type Bundle = Self;
    type Element = Element;

    fn apply(self, _root: &BSubtree, el: &Element) -> Self {
        #[expect(deprecated)]
        match &self {
            Self::Static(arr) => {
                for (k, v) in arr.iter() {
                    Self::set(el, k, v);
                }
            }
            Self::Dynamic { keys, values } => {

View on GitHub (pinned to 0e4a05472f)

Solutions

  1. Do not treat getter-only DOM properties (dataset, classList, isContentEditable, ...) as settable properties in Yew — use the dedicated APIs (classes!, attributes) instead
  2. Filter dynamic property names through an allowlist of writable properties before they reach the VTag
  3. Avoid freezing/sealing elements that Yew still needs to patch

Example fix

// before
// property map may contain getter-only keys like "classList"
for (k, v) in dynamic_props { tag.add_property(k, v); }

// after
const WRITABLE: &[&str] = &["value", "checked", "href", "innerHtml"];
for (k, v) in dynamic_props {
    if WRITABLE.contains(&k.as_str()) { tag.add_property(k, v); }
}
Defensive patterns

Strategy: validation

Validate before calling

// removal is Reflect::set(key, undefined) — only store properties that tolerate that write
const REMOVABLE_PROPS: &[&str] = &["value", "checked", "href", "selected"];
fn is_removable_prop(name: &str) -> bool { REMOVABLE_PROPS.contains(&name) }

Type guard

fn is_getter_only_dom_prop(name: &str) -> bool { matches!(name, "dataset" | "classList" | "isContentEditable") }

Prevention

When it happens

Trigger: A previous render stored a property whose DOM accessor is getter-only, and the next render drops that property so the removal path runs Reflect::set with undefined against a setter-less accessor; frozen elements likewise reject the write.

Common situations: Generic property-spread components forwarding arbitrary keys; transient properties (from a plugin or A/B tool) that disappear on the next render; elements sealed/frozen by tests or third-party scripts before re-render.

Related errors


AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22). Data as JSON: /api/errors/c02f176606cef01e. Report an issue: GitHub.