deferring-state-reads
Installation
SKILL.md
Deferring State Reads — Move Hot Reads from Composition to Draw
Compose runs three phases per frame — Composition → Layout → Draw. A state read at phase N invalidates phase N and every phase below it. The single biggest perf win in any animation- or scroll-driven UI is moving a state read from Composition down to Layout or Draw via a lambda-based modifier. This skill teaches Claude how to spot the wrong-phase read and migrate it.
When to use this skill
- An animation triggers full subtree recomposition on every frame (
Modifier.alpha(progress.value),Modifier.offset(x.dp),Modifier.padding(state.dp)—paddinghas no lambda overload, so useModifier.layout { ... }orModifier.offset { IntOffset(...) }instead when the inset is animated). - A scroll position is read directly in a composable body (
val y = scrollState.value) and the parent recomposes on every pixel of scroll. - The developer says "every frame", "scroll jank", "animation jank", "dropped frames", or reports the whole screen recomposing on drag.
- A
@TraceRecompositionlog shows the parent's recomposition counter incrementing once per animation tick. - The developer is passing a hot value (animation progress, scroll offset, drag delta) as a
Floatparameter across composables.
When NOT to use this skill
- The state changes once per user interaction (button click, dialog open) — the lambda-modifier rewrite buys nothing and adds noise.
- The state read genuinely needs to drive Composition (show/hide a different composable, swap a different component tree). Lambda modifiers cannot decide which composable to emit.
- The non-skippable parent is non-skippable for a different reason (unstable parameter). Diagnose with
../../stability/diagnosing-compose-stability/SKILL.mdfirst. - The developer wants to filter many high-frequency inputs into one rare boolean — that is
../choosing-derivedstateof/SKILL.md.