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
- Do not treat getter-only DOM properties (dataset, classList, isContentEditable, ...) as settable properties in Yew — use the dedicated APIs (classes!, attributes) instead
- Filter dynamic property names through an allowlist of writable properties before they reach the VTag
- 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
- Do not store getter-only DOM properties (dataset, classList) as VTag properties — they fail both set and unset
- Filter dynamic property names through a writable allowlist
- Never freeze/seal DOM nodes that Yew still reconciles
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
- could not set property
- invalid attribute key
- could not remove attribute
- can't create namespaced element for vtag
- unkeyed child in fully keyed list
AI-assisted analysis of yewstack/yew@0e4a05472f (2026-08-22).
Data as JSON: /api/errors/c02f176606cef01e.
Report an issue: GitHub.