Gestión de Estado con Context API y Hooks

Video
25 min~8 min lectura
Objetivo de la lección

Esta combinación es fundamental para construir aplicaciones nativas escalables, mantenibles y con un flujo de datos claro.

Puntos de control
  • Concepto Clave: Contexto y Hooks, el Sistema Nervioso de tu App
  • Cómo Funciona en la Práctica: Un Flujo en Tres Pasos
  • Código en Acción: Creando un Contexto de Autenticación
  • Errores Comunes y Cómo Evitarlos

Reproductor de video

Gestión de Estado con Context API y Hooks

Gestión de Estado con Context API y Hooks

En esta lección, profundizaremos en dos de las herramientas más poderosas y elegantes que React y React Native ofrecen para manejar el estado de tu aplicación: la Context API y los Hooks personalizados. Moverás tu gestión de estado más allá del ámbito local de un componente, aprendiendo a compartir datos y funciones de manera eficiente y predecible a través de toda tu jerarquía de componentes, sin la necesidad de pasar props manualmente en cada nivel (un problema conocido como prop drilling). Esta combinación es fundamental para construir aplicaciones nativas escalables, mantenibles y con un flujo de datos claro.

Concepto Clave: Contexto y Hooks, el Sistema Nervioso de tu App

Imagina que estás construyendo una gran ciudad (tu aplicación). Cada edificio es un componente. Los residentes (datos) necesitan moverse entre edificios. Podrías llevar a cada persona manualmente de un edificio a otro (pasar props), pero sería caótico e ineficiente. En su lugar, construyes una red de metro y un sistema de comunicación centralizado. La Context API es esa infraestructura compartida, un "conducto" o "canal" que puedes crear para transmitir datos a través de muchos componentes, sin que cada uno tenga que saber de dónde vienen o a dónde van.

Los Hooks, como useContext y los hooks personalizados, son los mecanismos de acceso a esa infraestructura. Son como las estaciones de metro y los boletos inteligentes que permiten a cada componente (edificio) suscribirse a la información que fluye por el contexto y reaccionar cuando ésta cambia. Juntos, forman un sistema nervioso para tu app: el contexto define las vías de comunicación principales, y los hooks permiten a cada parte del cuerpo (componente) sentir y responder a los estímulos (cambios de estado) de manera coordinada.

Tip: Context API no es un sistema de gestión de estado completo como Redux por sí solo. Es un mecanismo de inyección de dependencias. Para convertirlo en un gestor de estado, lo combinamos con el hook useReducer o con un estado local gestionado a través de un hook personalizado, creando un patrón poderoso y más simple que soluciones externas para muchos casos de uso.

Cómo Funciona en la Práctica: Un Flujo en Tres Pasos

La implementación sigue un patrón claro y repetible. Primero, creas el contexto utilizando React.createContext(). Esto produce un objeto con dos componentes cruciales: un Provider y un Consumer. El Provider es el componente de alto nivel que "provee" o inyecta el valor del contexto a todos sus componentes hijos. Piensa en él como la central eléctrica que suministra energía a toda la ciudad. Este valor puede ser cualquier cosa: un objeto, un array, una función, o, más comúnmente, un estado y funciones para modificarlo.

En segundo lugar, envuelves tu árbol de componentes con el Provider. Normalmente, esto se hace en un nivel alto de la aplicación, como el componente App o un componente de navegación principal. Todos los componentes dentro de este wrapper tendrán potencial acceso al contexto. Finalmente, en cualquier componente hijo que necesite acceder a los datos o funciones del contexto, utilizas el hook useContext(), pasándole el objeto de contexto que creaste. Este hook te suscribe al contexto y devuelve su valor actual, haciendo que tu componente se rerenderice automáticamente cuando ese valor cambie.

Para organizar y escalar este patrón, es una práctica excelente crear un hook personalizado (por ejemplo, useAppContext()) que encapsule la llamada a useContext. Esto centraliza la lógica de importación, proporciona un punto único de error si el contexto se usa fuera del Provider, y ofrece una API más limpia y declarativa para todos tus componentes.

Código en Acción: Creando un Contexto de Autenticación

Vamos a construir un ejemplo completo y funcional de un contexto para manejar el estado de autenticación de un usuario. Este es un caso de uso clásico donde muchos componentes (Header, Perfil, Pantallas protegidas) necesitan saber si el usuario está logueado y quién es.

Primero, creamos el archivo del contexto y el provider. Nota cómo combinamos useState para el estado y funciones para actualizarlo dentro del Provider.

// contexts/AuthContext.js
import React, { createContext, useState, useContext, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

// 1. Crear el Contexto
const AuthContext = createContext({});

// 2. Crear el Componente Provider
export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // Efecto para cargar el estado de autenticación desde AsyncStorage al iniciar
  useEffect(() => {
    const loadStoredData = async () => {
      try {
        const storedUser = await AsyncStorage.getItem('@user');
        if (storedUser) {
          setUser(JSON.parse(storedUser));
        }
      } catch (error) {
        console.error('Error cargando datos de autenticación:', error);
      } finally {
        setLoading(false);
      }
    };
    loadStoredData();
  }, []);

  // Función para iniciar sesión
  const signIn = async (email, password) => {
    // Simulación de una llamada a una API
    const mockUser = { id: '1', email, name: 'Usuario Ejemplo' };
    setUser(mockUser);
    await AsyncStorage.setItem('@user', JSON.stringify(mockUser));
  };

  // Función para cerrar sesión
  const signOut = async () => {
    setUser(null);
    await AsyncStorage.removeItem('@user');
  };

  // El valor que se provee a todos los componentes consumidores
  const value = {
    user,
    loading,
    signIn,
    signOut,
    isAuthenticated: !!user, // Propiedad computada
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

// 3. Crear un hook personalizado para consumir el contexto
export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth debe ser usado dentro de un AuthProvider');
  }
  return context;
};

Ahora, envolvemos nuestra aplicación con el AuthProvider en un punto alto, como el archivo App.js.

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { AuthProvider } from './contexts/AuthContext';
import AppNavigator from './navigation/AppNavigator';

export default function App() {
  return (
    <AuthProvider>
      <NavigationContainer>
        <AppNavigator />
      </NavigationContainer>
    </AuthProvider>
  );
}

Finalmente, cualquier componente de pantalla puede acceder al estado y las funciones de autenticación de manera sencilla usando nuestro hook personalizado useAuth.

// screens/ProfileScreen.js
import React from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
import { useAuth } from '../contexts/AuthContext';

const ProfileScreen = () => {
  // Uso limpio y declarativo del contexto
  const { user, signOut, loading, isAuthenticated } = useAuth();

  if (loading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <View style={{ flex: 1, padding: 20 }}>
      {isAuthenticated ? (
        <>
          <Text>Bienvenido, {user.name}!</Text>
          <Text>Email: {user.email}</Text>
          <Button title="Cerrar Sesión" />
        </>
      ) : (
        <Text>Por favor, inicia sesión.</Text>
      )}
    </View>
  );
};

export default ProfileScreen;

Errores Comunes y Cómo Evitarlos

Al trabajar con Context API y Hooks, es fácil caer en ciertos patrones que pueden degradar el rendimiento o la mantenibilidad de tu aplicación.

1. Proveer un valor que cambia constantemente en línea: Un error frecuente es pasar un objeto literal directamente en la prop value del Provider. Como este objeto se recrea en cada render, provoca rerenders innecesarios en todos los componentes suscritos, incluso si los datos reales no han cambiado.

Solución: Siempre memoiza el valor del contexto usando useMemo si su cálculo es costoso, o asegúrate de que la referencia del objeto permanezca estable si los datos no han cambiado. En nuestro ejemplo, el valor se deriva directamente del estado, por lo que es estable por render.

2. Crear un "Contexto God" o monolítico: Intentar almacenar todo el estado de la aplicación en un solo contexto. Esto acopla partes de la app que no están relacionadas y hace que cualquier cambio provoque rerenders masivos.

Solución: Divide tu estado en múltiples contextos lógicos (AuthContext, ThemeContext, CartContext). Los componentes se suscribirán solo al contexto que necesiten, mejorando la eficiencia y la organización.

3. Usar Contexto para estado verdaderamente local: No todo estado debe estar en un contexto. El estado que solo es relevante para un componente y sus hijos directos (como el valor de un campo de texto en un formulario) debe manejarse con useState local.

Solución: Usa Context API para estado global o compartido a través de múltiples ramas del árbol de componentes. Para estado local, useState y useReducer son suficientes.

4. Olvidar manejar el estado de "loading" o "error": En contextos que realizan operaciones asíncronas (como login, fetch de datos), es crucial exponer estados de carga y error para una experiencia de usuario fluida.

Solución: Como vimos en el ejemplo, incluye estados como loading y error en el valor del contexto y proporciona funciones para limpiarlos. Esto permite a los componentes mostrar spinners o mensajes de error apropiados.

Checklist de Dominio

Para asegurarte de que has comprendido y puedes aplicar los conceptos de esta lección, verifica que puedes realizar lo siguiente:

  • Explicar la diferencia entre pasar props (prop drilling) y usar Context API para compartir datos.
  • Crear un contexto desde cero usando React.createContext() y estructurar un componente Provider que gestione estado con useState o useReducer.
  • Envolver correctamente el árbol de componentes de una aplicación (normalmente en App.js) con un Context Provider.
  • Consumir un contexto en un componente funcional utilizando el hook useContext().
  • Crear y utilizar un hook personalizado (ej: useAppContext()) para encapsular el consumo de un contexto, mejorando la limpieza del código y el manejo de errores.
  • Identificar cuándo es apropiado usar Context API (estado global/compartido) y cuándo es mejor usar estado local.
  • Dividir la lógica de la aplicación en múltiples contextos específicos para evitar un "Contexto God" y optimizar los rerenders.
  • Manejar estados asíncronos (loading, error) dentro de un contexto que realiza operaciones como llamadas a API.
Falar no WhatsApp
De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión

Gestión de Estado con Context API y Hooks | Cursalo