Concepto clave
En React Native con Expo, la gestión de estado y la interfaz de usuario avanzada trabajan juntas como el cerebro y los músculos de una app. Imagina que estás construyendo un dashboard financiero: el estado (como el saldo actual o las transacciones recientes) es la información que cambia, mientras que la UI (botones, gráficos, listas) es cómo el usuario interactúa con esa información. Sin una gestión de estado eficiente, tu UI se vuelve lenta o inconsistente, similar a un cajero automático que muestra saldos incorrectos.
Para apps intermedias, necesitas ir más allá del estado local de componentes. Herramientas como Context API o Redux Toolkit te permiten compartir estado entre múltiples pantallas, manteniendo la UI sincronizada. Piensa en esto como un sistema centralizado de notificaciones en un edificio: cuando algo cambia (como un nuevo mensaje), todas las pantallas relevantes se actualizan automáticamente, sin necesidad de pasar datos manualmente entre componentes.
Cómo funciona en la práctica
Vamos a implementar un carrito de compras en una app de e-commerce. Primero, define el estado global usando Context API. Crea un archivo CartContext.js que almacene los items del carrito y funciones para agregar o eliminar productos. Luego, envuelve tu app con este contexto en App.js, permitiendo que cualquier componente acceda al carrito.
En la UI, usa componentes optimizados como FlatList para mostrar los items del carrito eficientemente, incluso con cientos de productos. Para interacciones, implementa botones que despachen acciones al contexto (ej., "agregar al carrito"), lo que actualizará el estado y re-renderizará solo los componentes afectados. Esto evita recargas completas de la pantalla, mejorando el rendimiento en dispositivos móviles.
Código en acción
Antes: Estado local en un componente, difícil de compartir.
import React, { useState } from 'react'; import { View, Text, Button } from 'react-native'; const ProductScreen = () => { const [cart, setCart] = useState([]); const addToCart = (product) => { setCart([...cart, product]); }; return ( Producto: CamisetaaddToCart('Camiseta')} /> ); }; export default ProductScreen;
Después: Estado global con Context API, fácil de acceder desde cualquier pantalla.
import React, { createContext, useContext, useReducer } from 'react'; import { View, Text, Button } from 'react-native'; const CartContext = createContext(); const cartReducer = (state, action) => { switch (action.type) { case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] }; case 'REMOVE_ITEM': return { ...state, items: state.items.filter(item => item.id !== action.payload) }; default: return state; } }; const CartProvider = ({ children }) => { const [state, dispatch] = useReducer(cartReducer, { items: [] }); return ( {children} ); }; const ProductScreen = () => { const { dispatch } = useContext(CartContext); const addToCart = () => { dispatch({ type: 'ADD_ITEM', payload: { id: 1, name: 'Camiseta', price: 20 } }); }; return ( Producto: Camiseta); }; export { CartProvider, ProductScreen };
Errores comunes
- Actualizar estado directamente: Modificar el estado sin usar
setStateo dispatches causa bugs. Siempre usa funciones inmutables. - No optimizar listas largas: Usar
ScrollViewpara listas con muchos items ralentiza la app. Cambia aFlatListconkeyExtractor. - Contextos anidados innecesariamente: Crear múltiples contextos para datos relacionados complica el código. Combina estados en un solo contexto cuando sea lógico.
- Olvidar limpiar suscripciones: En efectos (
useEffect), no remover listeners o intervalos puede causar fugas de memoria. Usa funciones de limpieza. - Ignorar el rendimiento en UI compleja: Componentes que se re-renderizan frecuentemente sin necesidad. Usa
React.memoouseMemopara evitar cálculos costosos.
Checklist de dominio
- ¿Puedes crear un contexto global que maneje estado compartido entre al menos 3 pantallas?
- ¿Implementas listas eficientes con
FlatListy propiedades comoinitialNumToRender? - ¿Usas reducers o estado local según la complejidad del componente?
- ¿Manejas estados de carga y error en la UI para mejorar la experiencia del usuario?
- ¿Optimizas re-renders con técnicas como
useCallbackpara funciones pasadas a hijos? - ¿Integras gestos táctiles (ej.,
PanResponder) con actualizaciones de estado? - ¿Pruebas la UI en ambos iOS y Android usando Expo Go para verificar consistencia?
Implementa un sistema de favoritos con estado global y UI optimizada
En este ejercicio, construirás una funcionalidad de favoritos para una app de recetas. Sigue estos pasos:
- Crea un nuevo proyecto Expo usando
npx create-expo-app RecipeFavorites. - Define un contexto
FavoritesContextque almacene un array de IDs de recetas favoritas. Incluye acciones para agregar y quitar favoritos. - Envuelve tu
AppconFavoritesProviderenApp.js. - Crea una pantalla
RecipeListScreenque muestre una lista de al menos 5 recetas (usa datos mock). Cada item debe tener un botón de "Favorito" que cambie de estado al presionarlo. - Implementa la lista con
FlatList, asegurándote de usarkeyExtractory propiedades de rendimiento comowindowSize. - Añade una pantalla
FavoritesScreenque muestre solo las recetas favoritas, accediendo al contexto global. - Prueba la app en Expo Go: verifica que al marcar/desmarcar favoritos, ambas pantallas se actualicen inmediatamente.
- Usa useReducer en el contexto para manejar acciones de manera predecible.
- Para el botón de favorito, cambia el ícono (ej., corazón vacío/lleno) basado en si el ID está en el array de favoritos.
- Si la lista de recetas es estática, considera usar useMemo para evitar re-crear datos en cada render.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.