State Hoisting in Jetpack Compose: Building Stateless, Testable UI Components
Implement state hoisting in Jetpack Compose to build stateless, reusable components that are easier to test and maintain.
10 Aug 2025, 21:05 UTC

The Problem: Stateful Composables Are Hard to Reuse
When you build a composable that manages its own state with remember { mutableStateOf() }, you create a component that is tightly coupled to its internal logic. This makes it impossible to pre-populate values from outside, test in isolation, or share across screens without duplicating code.
The Solution: Hoist State to the Caller
State hoisting moves state ownership from the child composable to its caller. Instead of managing state internally, the child receives two parameters: a value to display and an onEvent lambda to request changes. This makes the child stateless and pure—it only renders what it is given.
Worked Example: A Stateless TextField
Consider a text field that should be reusable across multiple screens. Here's how to hoist its state:
// Stateless composable - only renders UI
@Composable
fun SearchTextField(
query: String,
onQueryChange: (String) -> Unit
) {
TextField(
value = query,
onValueChange = onQueryChange,
label = { Text("Search...") }
)
}
// Stateful parent - owns the state
@Composable
fun SearchScreen(viewModel: SearchViewModel = viewModel()) {
val query by viewModel.query.collectAsState()
SearchTextField(
query = query,
onQueryChange = { viewModel.updateQuery(it) }
)
}
Trade-offs and Limitations
Prop drilling: Over-hoisting can force you to pass state through many unnecessary layers. Use CompositionLocal or shared ViewModels to avoid this.
Recomposition overhead: Changes to hoisted state trigger recompositions of the parent. Use derivedStateOf or stable/immutable types to minimize unnecessary work.
Verification Checklist
- Rotation test: Rotate the device. If state persists via ViewModel, hoisting is correct.
- Layout Inspector: Observe recomposition counts when state changes. Excessive recompositions indicate optimization needs.
- Preview test: Create a
@Previewwith mock data to verify the stateless composable renders correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.