Interference when using same class name in multiple cls <-- source mods
ngbinh opened this issue · comments
I encounter this quite subtle bug when trying to set cls
. I probably can do something else to avoid it but I think it is quite a big surprise for Laminar user
it("cls bug") {
val bus = new EventBus[Int]
mount(
div(
"hello",
cls <-- bus.events.map { num =>
if (num % 2 == 0) {
"always even"
} else {
""
}
},
cls <-- bus.events.map { num =>
if (num % 2 == 1) {
"always odd"
} else {
""
}
}
)
)
expectNode(div like("hello"))
bus.writer.onNext(1)
expectNode(
div like(
"hello",
cls is ("always odd")
)
)
bus.writer.onNext(2)
expectNode(
div like(
"hello",
cls is ("always even")
)
)
}
Ah, I see. It's a known limitation of the current design:
Each
cls <-- source
modifier should deal with a set of CSS classes that does not intersect with the sets used by other such modifiers. In other words, you should generally avoid adding classes to an element in one reactive modifier and then removing them in another reactive modifier. Basically, if you do this you have two competing sources of truth for whether an element should carry a class at any given time, and the latest source to emit will win.
Current implementation is simple – these two modifiers don't know about each other yet still work well together almost always – but well, "almost" is not "always", and here we are. I agree that it can be surprising if you actually run into it.
I think I can improve on this, but it would require for all cls
-based modifiers to read-write some shared state on every element they touch, basically it would work like reference counting for individual class names. Same for other composite keys.
I'll keep the issue open, meanwhile you should be able to refactor your use of cls
now that you know what the limitation is.
yep. Something like this
mount(
div(
"hello",
cls <-- bus.events.map { num =>
if (num % 2 == 0) "always even" else "always odd"
}
)
)
works as expected
Is anyone using cls.set
or cls.remove
? I'm considering removing one or both of those as part of fixing this, because they don't really fit in the non-conflicting paradigm. I may keep cls.forceRemove("class1")
as an escape hatch though.
@raquo we are not using it. Always cls <-- clsSignal
.