firstVisibleItemIndex changes with every row that scrolls past, but a "Back to top" label only cares whether it is above zero. ScrollDirect reads the test in its body; ScrollDerived wraps it in derivedStateOf, which invalidates readers only when the result changes:
@Composable
fun ScrollDerived(books: List<Book>) {
LogCompositions("ScrollDerived")
val listState = rememberLazyListState()
val showTop by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } }
BookScroller(books, listState, showTop)
}BookScroller builds 60 rows in remember(books, byPrice) { }, rerun only when a key changes, like useMemo. Three swipes on ScrollDerived, then Sort by price:
D/BookNestState: building rows byPrice=false D/BookNestState: ScrollDerived composed #1 D/BookNestState: BookScroller composed #1 D/BookNestState: ScrollDerived composed #2 D/BookNestState: BookScroller composed #2 D/BookNestState: building rows byPrice=true D/BookNestState: BookScroller composed #3
ScrollDerived recomposed once, when the flag flipped; ScrollDirect recomposed six times for the same swipes. The rows were rebuilt only when the byPrice key changed; without it, the stale order would stay.