Describe the bug
In Solid 2, an object passed to the class prop does not update the DOM when that object is mutated in place and its signal uses { equals: false }.
The signal correctly notifies its dependents, but the class runtime compares the new value against the same already-mutated object. Consequently, previously applied classes are not removed and newly enabled classes are not added.
style object values already handle this case by keeping a separate snapshot of the declarations applied to the DOM. The object form of class does not appear to keep an equivalent snapshot.
Steps to Reproduce the Bug or Issue
- Render the example.
- Inspect the target element. It initially has class="before".
- Click Update classes.
- Inspect the element again.
The following regression test also demonstrates the issue:
test("removes classes after an in-place object update", () => {
const classes = { before: true, after: false };
let div!: HTMLDivElement;
let notify!: () => void;
createRoot(() => {
const [value, setValue] = createSignal(classes, { equals: false });
notify = () => setValue(classes);
div = <div class={value()} /> as HTMLDivElement;
});
expect(div.className).toBe("before");
classes.before = false;
classes.after = true;
notify();
flush();
expect(div.className).toBe("after");
});
The final assertion currently fails:
Expected: "after"
Received: "before"
Expected behavior
After the signal notification, the target element should have:
<div id="target" class="after">Target</div>
The before class should be removed and the after class should be added.
equals: false is documented as always notifying dependents even when the signal value retains the same identity, so DOM bindings should process the updated object contents.
Platform
- Solid: next at f051db6
- Test environment: Vitest with jsdom
- OS: Linux
Additional context
className() receives the same object for both value and prev:
prev = classListToObject(prev || {});
value = classListToObject(value);
Because the object was already mutated before the signal notification, both normalized values contain the new state. The runtime therefore cannot determine which classes were previously applied to the DOM.
The nearby style() implementation avoids the same problem by storing a separate snapshot on the element:
let applied = node._$styles;
A similar per-element snapshot of classes applied by className() could allow in-place object updates to be diffed without removing unrelated classes.
Related but different: #2673 reported stale dependencies for an object nested inside a class array. That issue was closed as a dependency/build problem; this reproduction consistently reaches the class update
but loses the previous object state.
Describe the bug
In Solid 2, an object passed to the
classprop does not update the DOM when that object is mutated in place and its signal uses{ equals: false }.The signal correctly notifies its dependents, but the
classruntime compares the new value against the same already-mutated object. Consequently, previously applied classes are not removed and newly enabled classes are not added.styleobject values already handle this case by keeping a separate snapshot of the declarations applied to the DOM. The object form ofclassdoes not appear to keep an equivalent snapshot.Steps to Reproduce the Bug or Issue
The following regression test also demonstrates the issue:
The final assertion currently fails:
Expected: "after"
Received: "before"
Expected behavior
After the signal notification, the target element should have:
The before class should be removed and the after class should be added.
equals: false is documented as always notifying dependents even when the signal value retains the same identity, so DOM bindings should process the updated object contents.
Platform
Additional context
className() receives the same object for both value and prev:
prev = classListToObject(prev || {});
value = classListToObject(value);
Because the object was already mutated before the signal notification, both normalized values contain the new state. The runtime therefore cannot determine which classes were previously applied to the DOM.
The nearby style() implementation avoids the same problem by storing a separate snapshot on the element:
let applied = node._$styles;
A similar per-element snapshot of classes applied by className() could allow in-place object updates to be diffed without removing unrelated classes.
Related but different: #2673 reported stale dependencies for an object nested inside a class array. That issue was closed as a dependency/build problem; this reproduction consistently reaches the class update
but loses the previous object state.