Mastering State Hoisting in Jetpack Compose
Learn how to lift state in Jetpack Compose, turning stateful components into reusable stateless ones. Understand the benefits, trade‑offs, and a concrete example with a search bar.
19 Nov 2025, 04:48 UTC

The Problem: A Black‑Box Component
Imagine you built a custom search bar that keeps its own text using remember { mutableStateOf("") }. It works when you run the screen alone, but when the “Clear All” button lives elsewhere in the UI, the parent has no way to reset the text because the state is trapped inside the child.
What State Hoisting Does
State hoisting is the pattern of moving that internal state to the component’s caller. The child becomes stateless: it receives a value to render and a lambda that reports changes back to the caller.
Implementing Hoisting
Replace the internal remember with two parameters:
- value – the current state, e.g. a
String. - onValueChange – a lambda that the parent uses to update the source of truth.
This creates a unidirectional data flow: state flows down, events flow up.
Worked Example
@Composable
fun SearchField(
query: String,
onQueryChange: (String) -> Unit
) {
TextField(
value = query,
onValueChange = onQueryChange,
label = { Text("Search...") }
)
}
@Composable
fun SearchScreen() {
var searchQuery by remember { mutableStateOf("") }
Column {
SearchField(
query = searchQuery,
onQueryChange = { searchQuery = it }
)
Button(onClick = { searchQuery = "" }) {
Text("Clear Search")
}
}
}
Run the app, type in the field, and observe the Layout Inspector – the screen recomposes, pushing the new value back down into SearchField.
Trade‑Offs
1. Prop Drilling
Hoisting too high forces the same value and lambda to pass through many composables that don’t use it, making the code brittle.
2. Recompositions
Changing hoisted state triggers recomposition of the entire parent tree. For a large screen, a single character change can redraw unrelated UI.
When to Hoist
| Scenario | Decision | Why |
|---|---|---|
| State needed by multiple components | Hoist | Single source of truth |
| Transient UI state (e.g., animation flag) | Keep internal | Parent doesn’t need to know |
| State must survive rotation | Hoist to ViewModel | ViewModel survives configuration changes |
Actionable Takeaway
Before adding remember inside a composable, ask: “Does another screen or component need to know or modify this value?” If yes, hoist it. That keeps your UI declarative, testable, and easier to maintain.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.