State Hoisting: elevación de estado y flujo unidireccional en Compose

Autor: IT Sectr Publicado: 2026-06-28 Tiempo de lectura: 8 min

State Hoisting es un patrón en Jetpack Compose mediante el cual el estado se extrae de una función Composable hija y se traslada a la padre, mientras que la hija recibe datos a través de parámetros y notifica cambios mediante callbacks. Es una implementación del principio de flujo de datos unidireccional (UDF), donde el estado se eleva hacia arriba y los eventos descienden. Según Google Android Developers, 2026, State Hoisting hace que los componentes sean reutilizables, testeables y predecibles.

Puntos clave

  • State Hoisting extrae el estado del componente hijo al componente padre
  • UDF (Unidirectional Data Flow) — el estado fluye hacia abajo, los eventos hacia arriba
  • Parámetros del componente hijo: valor (T) + lambda (T) -> Unit
  • Reutilización — el estado elevado permite usar la misma función con diferentes fuentes de datos
  • Pruebas — State Hoisting simplifica los tests unitarios al aislar la lógica de la UI

Qué es State Hoisting en Jetpack Compose

State Hoisting es un patrón en el que una función Composable no posee el estado sino que lo recibe desde fuera. En lugar de usar var dentro de la función, se emplean dos parámetros: un valor para mostrar y un callback lambda para manejar los cambios. Técnicamente, esto significa que el componente hijo se vuelve stateless (sin estado propio), mientras que el padre es stateful (posee el estado).

Ejemplo: el componente TextField de Material3 no almacena el texto ingresado internamente. Acepta value: String y onValueChange: (String) -> Unit. El padre que llama a TextField declara var value by remember { mutableStateOf("") } y pasa value y onValueChange. Esto es State Hoisting clásico: TextField es un componente tonto (solo muestra y reporta la entrada), el padre es inteligente (posee el estado).

Stateless vs Stateful: Un componente stateless es más fácil de probar — no depende del estado interno, su comportamiento está totalmente determinado por los parámetros de entrada. Un componente stateful es conveniente para prototipado rápido pero más difícil de reutilizar: está fuertemente acoplado a una sola fuente de datos. State Hoisting te da la opción: cualquier componente puede hacerse stateless elevando el estado hacia arriba.

Flujo de datos unidireccional (UDF) y State Hoisting

UDF (Unidirectional Data Flow) es un principio arquitectónico donde los datos se mueven en una dirección: desde la fuente de verdad (ViewModel o Composable padre) hacia la UI, y los eventos fluyen en dirección opuesta. State Hoisting es la implementación de UDF a nivel de componentes individuales. En lugar de que cada componente decida cuándo y cómo cambiar su propio estado, notifica al padre sobre un evento, y el padre decide cómo cambiar el estado.

Ventajas de UDF: predecibilidad — el estado cambia en un solo lugar, eliminando condiciones de carrera; trazabilidad — la pila de llamadas permite reconstruir la cadena de cambios; pruebas — la lógica stateful puede extraerse a una clase separada y probarse sin UI. En proyectos grandes, UDF combinado con State Hoisting es el estándar de facto.

Fuente única de verdad (Single Source of Truth) es otro principio que acompaña a UDF. Cada fragmento de estado tiene exactamente una fuente. Si dos componentes usan el mismo estado, la fuente debe compartirse (a nivel de ViewModel o padre común). State Hoisting asegura que la fuente esté arriba en la jerarquía y no se produzca duplicación de estado.

DirecciónQué se pasaCómo se implementa
Abajo (padre → hijo)Valor a mostrarParámetro value: T
Arriba (hijo → padre)Evento de cambioParámetro onValueChange: (T) -> Unit

Reglas de State Hoisting: cuándo y cómo elevar el estado

La regla principal: el estado debe elevarse al nivel mínimo posible suficiente para todos los componentes que lo necesiten. Si el estado solo se usa dentro de un componente — manténgalo local. Si dos componentes adyacentes necesitan el mismo estado — elévelo al padre común. Si el estado se necesita en toda la pantalla — elévelo a la ViewModel.

La regla de elevación mínima evita la complejidad innecesaria. No tiene sentido elevar el estado de un campo de texto a una ViewModel si solo se usa dentro de una pantalla y no se persiste al recrear la Activity. Use rememberSaveable a nivel del padre de la pantalla, no en ViewModel, para el estado de UI que debe sobrevivir a una rotación de pantalla pero que la lógica de negocio no necesita.

Cuándo elevar a ViewModel: si el estado debe sobrevivir a la recreación de Activity, si varios screens lo necesitan, si cambiar el estado dispara lógica de negocio (peticiones de red, base de datos). State Hoisting a nivel de ViewModel es un patrón estándar en la arquitectura MVVM, donde la capa de UI es stateless y la ViewModel es stateful.

kotlin
    // ❌ Mal: el componente posee su propio estado
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ Bien: State Hoisting — estado en el padre
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // Uso: el padre posee el estado
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

Ejemplos de State Hoisting en componentes reales

Considere una pantalla de inicio de sesión con dos campos (email, contraseña) y un botón. Los tres componentes reciben estado a través de State Hoisting: el email y la contraseña son gestionados por el padre, el botón recibe su estado enabled como valor.

kotlin
    // State Hoisting a nivel de pantalla
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // Campo Email — State Hoisting mediante lambda
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // Campo Contraseña — igualmente
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // Botón — recibe solo enabled (solo lectura)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// Componente Stateless: recibe email + callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        singleLine = true
    )
}

// Componente de botón Stateless
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("Iniciar sesión")
    }
}

EmailField y PasswordField son completamente stateless. Pueden reutilizarse en cualquier pantalla conectándolos a cualquier fuente de datos. LoginButton recibe enabled como solo lectura — esta es otra forma de State Hoisting donde el estado no se eleva (el botón no puede habilitarse solo) sino que se pasa ya preparado. Este enfoque proporciona máxima flexibilidad con el mínimo acoplamiento entre componentes.

State Hoisting vs estado local: criterios de selección

No todo estado necesita ser elevado. El estado local (State dentro de un Composable) está justificado cuando: los datos solo se necesitan dentro de un componente, no afectan a elementos hermanos y no deben sobrevivir a la recomposición de una sección específica. Por ejemplo, el estado de animación, el foco de un campo de entrada, la posición actual de desplazamiento — es razonable mantenerlos localmente.

Cuándo es necesario State Hoisting: el estado lo usan varios componentes hijos; un cambio en un hijo debe reflejarse en otro; la lógica de cambios de estado debe probarse separada de la UI; el estado debe sobrevivir a la recreación de Activity. En estos casos, el estado local crea duplicación e inconsistencia de datos.

Enfoque híbrido: mantenga el estado mínimo localmente, eleve el resto. La regla de Compose: “eleve el estado tan arriba como sea necesario y tan abajo como sea posible.” En la práctica, esto significa comenzar con remember local y solo elevar el nivel cuando se necesite acceso desde otro componente. No aplique State Hoisting de forma preventiva — complica el código sin necesidad.

Preguntas frecuentes

¿En qué se diferencia State Hoisting de ViewModel?

State Hoisting es un patrón a nivel de componentes de UI. ViewModel es una capa arquitectónica para la lógica de negocio. State Hoisting puede elevar el estado al nivel del Composable padre, al nivel de pantalla o a la ViewModel. ViewModel es el punto más alto de elevación para el estado que debe sobrevivir a la recreación de Activity.

¿Cómo probar un componente con State Hoisting?

Un componente stateless se prueba simplemente pasando valores. Llame al Composable con los parámetros requeridos y verifique la visualización mediante ComposeTestRule. Los cambios de estado se prueban a nivel del padre o de la ViewModel — separados de la UI. Esto simplifica significativamente las pruebas: no es necesario simular la recomposición dentro del componente.

¿Se puede elevar el Estado como solo lectura?

Sí, es una práctica común. Si un componente solo necesita mostrar datos sin posibilidad de modificarlos — pase State<T> (solo lectura). El componente se suscribirá a los cambios pero no podrá iniciarlos. Esto fortalece la encapsulación y protege los datos de mutaciones no deseadas.

¿Qué hacer si el estado debe elevarse 3+ niveles de profundidad?

Para pasar en profundidad use CompositionLocal o pase a través de los parámetros del Composable padre. Si el estado se necesita en toda la pantalla — extráigalo a una ViewModel y use collectAsState(). Pasar a través de 5+ niveles es señal de arquitectura incorrecta; reconsidere la jerarquía de componentes.

¿Afecta State Hoisting al rendimiento?

State Hoisting puede aumentar ligeramente el número de recomposiciones, ya que un cambio en el padre puede recomponer a todos los hijos. Use derivedStateOf para filtrar cambios y keys en LazyColumn para actualizaciones selectivas. En la mayoría de escenarios, la sobrecarga de State Hoisting es insignificante comparada con el beneficio de mantenibilidad.

Resumen

  • State Hoisting traslada el estado al componente padre, haciendo al hijo stateless
  • UDF garantiza flujo de datos unidireccional: estado hacia abajo, eventos hacia arriba
  • Reutilización — los componentes stateless pueden conectarse a cualquier fuente de datos
  • Pruebas — los tests de UI solo verifican la visualización, la lógica se prueba por separado
  • Elevación mínima — eleve solo lo necesario
  • ViewModel — el punto más alto de elevación para estado con lógica de negocio
  • Recomendación: comience con remember local, eleve solo cuando sea necesario

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también