📋 Tabla de contenidos
-
useState
– El fundamento del estado -
useEffect
– Efectos secundarios y ciclo de vida -
useContext
– Compartir estado sin props -
useReducer
– Lógica de Estado Compleja y predecible -
useRef
– Referencias, Almacenamiento y Escape -
useMemo
yuseCallback
– Optimizando el Rendimiento - Custom Hooks – Creando Lógica Reutilizable
- Errores Comunes y Soluciones
¿Qué es y para qué sirve?
useContext
resuelve un problema muy común en React llamado "prop drilling". Esto ocurre cuando tienes que pasar props a través de múltiples niveles de componentes anidados, incluso si los componentes intermedios no las necesitan.
useContext
te permite crear un "almacén" de datos global o semi-global al que cualquier componente dentro de un árbol especÃfico puede acceder directamente, sin necesidad de recibirlo por props.
Úsalo para:
- Gestionar el tema de la aplicación (oscuro/claro).
- Información del usuario autenticado.
- Configuraciones de idioma.
- Estado de un carrito de compras.
Sintaxis y flujo de trabajo
El uso de Context API se divide en 3 pasos:
-
Crear el Contexto:
// theme-context.js import { createContext } from 'react'; // El valor 'light' es el valor por defecto, solo se usa si un // componente intenta consumir el contexto sin un Provider por encima. export const ThemeContext = createContext('light');
-
Proveer el Contexto:
En algún lugar arriba en tu árbol de componentes (comoApp.js
), envuelve a los componentes hijos con elProvider
del contexto y pásale unvalue
.
// App.js import { ThemeContext } from './theme-context'; function App() { const [theme, setTheme] = useState('dark'); // Todos los componentes dentro de este Provider podrán acceder al valor 'dark'. return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Layout /> </ThemeContext.Provider> ); }
-
Consumir el Contexto:
Cualquier componente hijo puede usar el hookuseContext
para leer (y en este caso, modificar) el valor.
// Button.js import { useContext } from 'react'; import { ThemeContext } from './theme-context'; function Button() { const { theme, setTheme } = useContext(ThemeContext); const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light'); return <button onClick={toggleTheme}>Cambiar a tema {theme === 'light' ? 'oscuro' : 'claro'}</button>; }
Ejemplo práctico detallado: proveedor de autenticación
Vamos a crear un sistema simple para manejar el estado de autenticación de un usuario en toda la app.
1. Crear el AuthContext.js
// contexts/AuthContext.js
import React, { createContext, useState, useContext } from 'react';
// 1. Crear el contexto
const AuthContext = createContext(null);
// 2. Crear el Proveedor (un componente personalizado)
export function AuthProvider({ children }) {
const [user, setUser] = useState(null); // El usuario no está logueado por defecto
const login = (username) => setUser({ name: username });
const logout = () => setUser(null);
// Pasamos tanto el estado como las funciones para modificarlo
const value = { user, login, logout };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// 3. Crear un Custom Hook para consumir el contexto (buena práctica)
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth debe ser usado dentro de un AuthProvider');
}
return context;
}
2. Envolver la App con AuthProvider
// App.js
import { AuthProvider } from './contexts/AuthContext';
import Navbar from './components/Navbar';
function App() {
return (
<AuthProvider>
<h1>Mi Aplicación</h1>
<Navbar />
{/* ...el resto de la app... */}
</AuthProvider>
);
}
3. Usar el contexto en cualquier componente
// components/Navbar.js
import { useAuth } from '../contexts/AuthContext';
function Navbar() {
const { user, login, logout } = useAuth(); // ¡Asà de fácil!
return (
<nav>
{user ? (
<>
<span>Bienvenido, {user.name}</span>
<button onClick={logout}>Cerrar Sesión</button>
</>
) : (
<button onClick={() => login('UsuarioPrueba')}>Iniciar Sesión</button>
)}
</nav>
);
}
✅ Buenas practicas y patrones comunes
- Crear un Custom Provider: Como en el ejemplo anterior, es una excelente práctica crear un componente
MiProveedor
que encapsule la lógica del estado (useState
ouseReducer
) junto con elContext.Provider
. Esto mantiene la lógica centralizada y el componenteApp
más limpio. - Crear un Custom Hook consumidor:
useMiContexto
(comouseAuth
) hace que el consumo sea más declarativo y permite añadir validaciones, como comprobar si el hook se está usando dentro del proveedor correcto. - Separar contextos: No pongas todo el estado de tu aplicación en un solo contexto gigante. DivÃdelo por dominios:
AuthContext
,ThemeContext
,CartContext
, etc. Esto evita re-renderizados innecesarios. - Optimización de
value
: Cada vez que el componente que contiene alProvider
se re-renderiza, se crea un nuevo objeto paravalue
. Si este objeto contiene funciones, créalas conuseCallback
para evitar re-renderizados innecesarios en los componentes consumidores.
🚨 Errores comunes y cómo evitarlos
- Error: Olvidar envolver los componentes con el
<MiContext.Provider>
.- Solución: Asegúrate de que el
Provider
esté en un nivel superior en el árbol de componentes. El custom hook con la validación (como en el ejemplo deuseAuth
) te ayudará a detectar este error rápidamente.
- Solución: Asegúrate de que el
-
Error: Re-renderizados excesivos.
// PROBLEMA 😬 function App() { const [count, setCount] = useState(0); // El objeto `value` se crea de nuevo en CADA render de App, // incluso si `count` es el que cambia. Esto hace que TODOS los // consumidores de ThemeContext se re-rendericen. return ( <ThemeContext.Provider value={{ theme: 'dark' }}> <button onClick={() => setCount(c => c + 1)}>{count}</button> <MiComponentePesado /> </ThemeContext.Provider> ); }
- Solución: Saca el
value
del flujo del render si es estático, o memorÃzalo conuseMemo
si depende de props o estado.
// BIEN ✅ const themeValue = useMemo(() => ({ theme: 'dark' }), []); // O mejor aún, si es totalmente estático: const themeValue = { theme: 'dark' }; return <ThemeContext.Provider value={themeValue}>...</ThemeContext.Provider>;
- Solución: Saca el
🚀 Retos prácticos
- Selector de Idioma: Crea un
LanguageContext
que permita a los componentes mostrar texto en "Español" o "Inglés". Unos botones en laNavbar
deberÃan poder cambiar el idioma de toda la aplicación. - Notificaciones Globales: Implementa un
NotificationContext
que permita a cualquier componente mostrar un mensaje de notificación (ej. "Producto añadido al carrito"). Debe tener una funciónshowNotification(message)
. - Carrito de Compras con
useReducer
yuseContext
: Combina ambos hooks. Crea unCartContext
que use unuseReducer
internamente para manejar la lógica de añadir, eliminar y actualizar productos en el carrito.
Top comments (0)