diff --git a/core/src/jsMain/kotlin/dev/fritz2/core/tags.kt b/core/src/jsMain/kotlin/dev/fritz2/core/tags.kt index 9587e22ca..cc66f5ceb 100644 --- a/core/src/jsMain/kotlin/dev/fritz2/core/tags.kt +++ b/core/src/jsMain/kotlin/dev/fritz2/core/tags.kt @@ -361,7 +361,10 @@ open class HtmlTag( } override fun className(value: Flow, initial: String) { - classesStateFlow.value += value.stateIn(MainScope() + job, SharingStarted.Eagerly, initial) + classesStateFlow.value += value + .catch { printErrorIgnoreLensException(it) } + .stateIn(MainScope() + job, SharingStarted.Eagerly, initial) + // this ensures that the set state is applied *immediately* without `Flow`-"delay". // in this case, the `initial` value gets applied as "promised". attr("class", buildClasses()) diff --git a/core/src/jsTest/kotlin/dev/fritz2/core/tags.kt b/core/src/jsTest/kotlin/dev/fritz2/core/tags.kt index d816e8cc3..ae8de5455 100644 --- a/core/src/jsTest/kotlin/dev/fritz2/core/tags.kt +++ b/core/src/jsTest/kotlin/dev/fritz2/core/tags.kt @@ -543,4 +543,49 @@ class TagTests { assertEquals("fourth", getAttribute()) } + @Test + fun testClassNameWithFlowInsideReactiveScopeBasedUponListMapping_WontThrowUnhandledCollectionLensGetException() = + runTest { + val items = listOf("red", "green", "blue") + + fun extractClasses() = items.mapNotNull { item -> document.getElementById(item) } + .joinToString(",") { it.className } + + val storedItems = storeOf(items, Job()) + val removeItem = storedItems.handle { allItems, toRemove -> + allItems.filterNot { it == toRemove } + } + + render { + storedItems.data.renderEach { item -> + val storedItem = storedItems.mapByElement(item) { it } + div(id = item) { + // access to mapped item will throw `CollectionLensGetException` after deletion + // -> must be catched internally like handlers do! + className(storedItem.data) + + // we give some time to the `className`-function to try to access a mapped lens for an object + // that might not exist anymore. + beforeUnmount { _, _ -> delay(100) } + } + } + } + + delay(100) + assertEquals("red,green,blue", extractClasses()) + + // this is the source of possible problem: By removing mapped lenses won't find their targeting object + // anymore. This will lead to `CollectionLensGetException`, that must be handled internally! + removeItem("green") + + delay(200) + assertEquals("red,blue", extractClasses()) + + removeItem("blue") + + // if not exception handling on the `classes`-Flow is done, rendering will freeze and the node for green + // will remain. + delay(200) + assertEquals("red", extractClasses()) + } } \ No newline at end of file