React Context API
Compartición de estado integrada usando Context API de React y el hook useContext
React Context API
Context proporciona una forma de pasar datos a través del árbol de componentes sin tener que pasar props manualmente en cada nivel. Está integrado en React y es perfecto para compartir datos "globales" como temas, autenticación de usuario o preferencias de idioma.
Cuándo Usar Context
- Tema — Modo claro/oscuro en toda la app
- Autenticación de Usuario — Datos del usuario actual
- Locale — Preferencias de idioma y formato
- Feature Flags — Activar/desactivar funciones
- Evitar Prop Drilling — Datos necesarios por muchos componentes
Creando y Usando Context
import { createContext, useContext, useState } from 'react';
// 1. Crear el context con un valor por defecto
const ThemeContext = createContext('light');
// 2. Crear un componente provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// El objeto value contiene todo lo que los consumidores necesitan
const value = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. Hook personalizado para consumo fácil
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme debe usarse dentro de un ThemeProvider');
}
return context;
}
// 4. Usar en componentes
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={theme === 'dark' ? 'bg-gray-900' : 'bg-white'}>
<button onClick={toggleTheme}>
Cambiar a modo {theme === 'light' ? 'oscuro' : 'claro'}
</button>
</header>
);
}
// 5. Envolver tu app con el provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<Footer />
</ThemeProvider>
);
}
Ejemplo de Context de Autenticación
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [usuario, setUsuario] = useState(null);
const [cargando, setCargando] = useState(true);
useEffect(() => {
// Verificar sesión existente al montar
const verificarAuth = async () => {
try {
const response = await fetch('/api/auth/me');
if (response.ok) {
const datosUsuario = await response.json();
setUsuario(datosUsuario);
}
} catch (error) {
console.error('Verificación de auth falló:', error);
} finally {
setCargando(false);
}
};
verificarAuth();
}, []);
const iniciarSesion = async (email, contrasena) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, contrasena }),
});
if (response.ok) {
const datosUsuario = await response.json();
setUsuario(datosUsuario);
return { exito: true };
}
return { exito: false, error: 'Credenciales inválidas' };
};
const cerrarSesion = async () => {
await fetch('/api/auth/logout', { method: 'POST' });
setUsuario(null);
};
const value = {
usuario,
cargando,
iniciarSesion,
cerrarSesion,
estaAutenticado: !!usuario,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth debe usarse dentro de un AuthProvider');
}
return context;
}
// Uso en componentes
function PaginaPerfil() {
const { usuario, cargando, cerrarSesion } = useAuth();
if (cargando) return <div>Cargando...</div>;
if (!usuario) return <div>Por favor inicia sesión</div>;
return (
<div>
<h1>¡Bienvenido, {usuario.nombre}!</h1>
<button onClick={cerrarSesion}>Cerrar Sesión</button>
</div>
);
}
Múltiples Contexts
// Componer múltiples providers
function App() {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<NotificationProvider>
<Router>
<AppContent />
</Router>
</NotificationProvider>
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
// O crear un provider combinado
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
Optimizando el Rendimiento de Context
import { createContext, useContext, useState, useMemo, useCallback } from 'react';
// Problema: La referencia del objeto cambia en cada render
function BadProvider({ children }) {
const [cuenta, setCuenta] = useState(0);
// ❌ Nuevo objeto cada render = re-renders innecesarios
const value = { cuenta, setCuenta };
return <Context.Provider value={value}>{children}</Context.Provider>;
}
// Solución: Memorizar el objeto value
function GoodProvider({ children }) {
const [cuenta, setCuenta] = useState(0);
// ✅ Memorizar el value
const value = useMemo(() => ({
cuenta,
incrementar: () => setCuenta(c => c + 1),
decrementar: () => setCuenta(c => c - 1),
}), [cuenta]);
return <Context.Provider value={value}>{children}</Context.Provider>;
}
// Mejor: Dividir en contexts separados
const CountContext = createContext(0);
const CountActionsContext = createContext(null);
function OptimizedProvider({ children }) {
const [cuenta, setCuenta] = useState(0);
// Las acciones nunca cambian, así que los componentes usando solo acciones no se re-renderizarán
const acciones = useMemo(() => ({
incrementar: () => setCuenta(c => c + 1),
decrementar: () => setCuenta(c => c - 1),
}), []);
return (
<CountContext.Provider value={cuenta}>
<CountActionsContext.Provider value={acciones}>
{children}
</CountActionsContext.Provider>
</CountContext.Provider>
);
}
// Ahora los componentes pueden suscribirse solo a lo que necesitan
function Display() {
const cuenta = useContext(CountContext); // Se re-renderiza cuando cuenta cambia
return <span>{cuenta}</span>;
}
function Buttons() {
const { incrementar, decrementar } = useContext(CountActionsContext);
// ¡Nunca se re-renderiza cuando cuenta cambia!
return (
<div>
<button onClick={decrementar}>-</button>
<button onClick={incrementar}>+</button>
</div>
);
}
Pros y Contras de Context API
| Pros | Contras |
|---|---|
| Integrado en React, sin dependencias extra | Todos los consumidores se re-renderizan en cualquier cambio de valor |
| API simple, fácil de entender | Sin devtools integradas |
| Perfecto para actualizaciones de baja frecuencia | Puede volverse verboso con múltiples contexts |
| Funciona con SSR desde el inicio | Problemas de rendimiento con actualizaciones frecuentes |
💡 Mejores Prácticas
- • Siempre crea un hook personalizado (useTheme, useAuth) para consumir el context
- • Lanza un error si el hook se usa fuera del provider
- • Mantén los contexts pequeños y enfocados en una sola preocupación
- • Memoriza el objeto value para prevenir re-renders innecesarios
- • Considera bibliotecas externas para actualizaciones frecuentes