Kodokon kodokon.com

Compartir estado: elevar el estado y callbacks

Mantén el estado en el ancestro común y convierte tus widgets hijos en widgets controlados que informan de los cambios mediante callbacks.

8 min · 3 preguntas

Abrir esta lección en Kodokon

Una situación clásica: un selector de cantidad y un total mostrado en otro lugar necesitan compartir los mismos datos. La regla de oro: el estado vive en el ancestro común más cercano de los widgets que dependen de él. El padre posee los datos, los transmite como un parámetro y recibe los cambios mediante un callback que fluye de vuelta hacia arriba. Esto es "elevar el estado" (lifting state up).

DART
import 'package:flutter/material.dart';

class CartScreen extends StatefulWidget {
  const CartScreen({super.key});

  @override
  State<CartScreen> createState() => _CartScreenState();
}

class _CartScreenState extends State<CartScreen> {
  int _quantity = 1;

  void _updateQuantity(int value) {
    setState(() => _quantity = value);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cart')),
      body: Column(
        children: [
          QuantitySelector(
            quantity: _quantity,
            onChanged: _updateQuantity,
          ),
          Text('Total: ${_quantity * 20} €'),
        ],
      ),
    );
  }
}
El padre posee el estado y reparte el valor y el callback.

El hijo se convierte en un widget controlado: es un StatelessWidget, no almacena nada, muestra el valor que recibe y señala cada intención mediante onChanged. ValueChanged<int> es simplemente un alias de void Function(int). Fíjate en el botón de menos desactivado en la cantidad 1: basta con pasar null a onPressed, y esta lógica se deriva directamente del valor recibido.

DART
import 'package:flutter/material.dart';

class QuantitySelector extends StatelessWidget {
  const QuantitySelector({
    super.key,
    required this.quantity,
    required this.onChanged,
  });

  final int quantity;
  final ValueChanged<int> onChanged;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: const Icon(Icons.remove),
          onPressed: quantity > 1
              ? () => onChanged(quantity - 1)
              : null,
        ),
        Text('$quantity'),
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () => onChanged(quantity + 1),
        ),
      ],
    );
  }
}
El hijo muestra el valor que recibe e informa de las intenciones hacia arriba.

Usas este contrato todo el tiempo sin darte cuenta: Checkbox, Slider, Switch son todos widgets controlados (value + onChanged). Cuando el árbol se hace más profundo y pasar los callbacks a través de cinco niveles se vuelve tedioso, será el momento de descubrir las soluciones de gestión de estado: ese es el tema del próximo módulo.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Dónde deberías colocar el estado compartido por dos widgets hermanos?
    • En cada uno de los dos widgets
    • En el ancestro común más cercano
    • En una variable global
    • En el MaterialApp
  2. ¿Cómo solicita un widget hijo un cambio en el estado?
    • Llama a setState directamente sobre su padre
    • Modifica la propiedad que recibió como parámetro
    • Invoca el callback proporcionado por el padre
  3. ¿Qué es un widget controlado?
    • Un widget que recibe su valor e informa de los cambios mediante un callback, sin estado propio
    • Un widget que debe usar setState
    • Un widget cubierto por pruebas unitarias
    • Un widget declarado const