Concepto clave
La Context API es un mecanismo de React para compartir datos entre componentes sin necesidad de pasar props manualmente a través de múltiples niveles (prop drilling). Imagina que tienes una aplicación de comercio electrónico con un carrito de compras. El carrito debe ser accesible desde la barra de navegación, la página de productos y la página de pago. Sin Context, tendrías que pasar el estado del carrito como prop desde el componente raíz hasta cada componente que lo necesite, lo que resulta en código verboso y difícil de mantener. Con Context, puedes crear un proveedor que envuelva la aplicación y un consumidor (o el hook useContext) que acceda al valor desde cualquier componente hijo.
La analogía del mundo real: piensa en un sistema de megafonía en un edificio. El proveedor es el altavoz central que emite un mensaje (el estado), y cualquier persona en el edificio (componente) puede escucharlo sin necesidad de que alguien le transmita el mensaje individualmente. Así, el estado global se vuelve accesible de forma eficiente y desacoplada.
Cómo funciona en la práctica
Para crear un proveedor de estado con Context API, sigues estos pasos:
- Crear el contexto con
React.createContext(). Esto devuelve un objeto con dos componentes:ProvideryConsumer. - Crear un componente proveedor que envuelva a los hijos y pase un valor a través del
Provider. Normalmente, este proveedor maneja el estado conuseStateouseReducer. - Consumir el contexto en cualquier componente hijo usando el hook
useContexto el componenteConsumer.
Ejemplo paso a paso: Supongamos que queremos un contexto de tema (claro/oscuro). Primero, creamos el archivo ThemeContext.js:
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
Luego, en el componente raíz (App.js), envolvemos la aplicación con el proveedor:
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import Header from './Header';
function App() {
return (
<ThemeProvider>
<Header />
</ThemeProvider>
);
}
export default App;
Finalmente, en el componente Header.js, consumimos el contexto:
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function Header() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<header style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={toggleTheme}>Cambiar tema</button>
</header>
);
}
export default Header;
Código en acción
Aquí tienes un ejemplo completo y funcional de un proveedor de carrito de compras. Crea un archivo CartContext.js:
import React, { createContext, useReducer, useContext } from 'react';
const CartContext = createContext();
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.payload];
case 'REMOVE_ITEM':
return state.filter(item => item.id !== action.payload.id);
default:
return state;
}
};
export const CartProvider = ({ children }) => {
const [cart, dispatch] = useReducer(cartReducer, []);
const addItem = (item) => dispatch({ type: 'ADD_ITEM', payload: item });
const removeItem = (item) => dispatch({ type: 'REMOVE_ITEM', payload: item });
return (
<CartContext.Provider value={{ cart, addItem, removeItem }}>
{children}
</CartContext.Provider>
);
};
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart debe usarse dentro de un CartProvider');
}
return context;
};
Luego, en un componente ProductList.js:
import React from 'react';
import { useCart } from './CartContext';
const products = [
{ id: 1, name: 'Camiseta', price: 20 },
{ id: 2, name: 'Pantalón', price: 40 },
];
function ProductList() {
const { addItem } = useCart();
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
<button onClick={() => addItem(product)}>Agregar al carrito</button>
</li>
))}
</ul>
);
}
export default ProductList;
Y en Cart.js:
import React from 'react';
import { useCart } from './CartContext';
function Cart() {
const { cart, removeItem } = useCart();
return (
<ul>
{cart.map(item => (
<li key={item.id}>
{item.name} - ${item.price}
<button onClick={() => removeItem(item)}>Eliminar</button>
</li>
))}
</ul>
);
}
export default Cart;
Este ejemplo muestra cómo crear un proveedor con useReducer para manejar lógica más compleja, y cómo exponer un hook personalizado useCart para facilitar el consumo.
Errores comunes
- No envolver la aplicación con el proveedor: Si olvidas colocar el
Provideren un nivel superior, los componentes que intenten consumir el contexto lanzarán un error o recibiránundefined. Solución: Siempre verifica que el proveedor esté en un ancestro común. - Crear un nuevo contexto cada render: Si defines el contexto dentro de un componente que se renderiza frecuentemente, se creará un nuevo objeto de contexto cada vez, causando que los consumidores se vuelvan a renderizar innecesariamente. Solución: Define el contexto fuera del componente, en un archivo separado.
- Pasar objetos o funciones sin memoizar: Si el valor del proveedor es un objeto o función creado en cada render, todos los consumidores se re-renderizarán aunque el estado no haya cambiado. Solución: Usa
useMemoouseCallbackpara estabilizar las referencias. - Usar Context para todo el estado global: Context no está diseñado para reemplazar bibliotecas de estado global como Redux en aplicaciones grandes. Para estados que cambian frecuentemente, Context puede causar renders innecesarios en toda la aplicación. Solución: Evalúa si realmente necesitas estado global o si puedes usar estado local o lifting state up.
- No validar que el contexto esté disponible: Al usar
useContext, si el componente está fuera del proveedor, obtendrá el valor por defecto (que suele serundefined). Solución: Crea un hook personalizado que lance un error si no hay proveedor, como en el ejemplo deuseCart.
Checklist de dominio
- [ ] He creado un contexto con
createContexty lo he exportado. - [ ] He creado un componente proveedor que maneja el estado con
useStateouseReducer. - [ ] He envuelto la aplicación (o parte de ella) con el proveedor.
- [ ] He consumido el contexto en al menos un componente hijo usando
useContext. - [ ] He creado un hook personalizado (ej.
useCart) para consumir el contexto con validación. - [ ] He verificado que los consumidores no se re-rendericen innecesariamente (usando React DevTools o console.log).
- [ ] He evitado pasar objetos/funciones sin memoizar en el valor del proveedor.
Para verificar que tu implementación es correcta, abre las herramientas de desarrollo de React y observa el árbol de componentes. Deberías ver el proveedor como un nodo en la jerarquía, y los consumidores deberían mostrar el valor del contexto sin errores.
Crear un proveedor de autenticación con Context API
Objetivo
Implementar un contexto de autenticación que permita a los usuarios iniciar sesión y cerrar sesión, y que exponga el estado de autenticación a toda la aplicación.
Entregable
Un archivo AuthContext.js que contenga el contexto, el proveedor y un hook personalizado useAuth. Además, un componente LoginButton.js que use el hook para mostrar un botón de inicio/cierre de sesión.
Pasos
- Crea un archivo
AuthContext.js. - Define un contexto con
createContext. - Crea un componente
AuthProviderque gestione el estadouser(inicialmentenull) y una funciónloginque reciba un objeto usuario y lo establezca, y una funciónlogoutque lo ponga anull. - Pasa
{ user, login, logout }como valor alProvider. - Crea un hook
useAuthque llame auseContexty lance un error si no hay proveedor. - Exporta
AuthProvideryuseAuth. - En un componente
LoginButton.js, usauseAuthpara mostrar un botón: siuseresnull, muestra "Iniciar sesión" y llama alogincon un objeto de prueba; si hay usuario, muestra "Cerrar sesión" y llama alogout. - Envuelve tu componente
AppconAuthProvidery renderizaLoginButton.
Criterios de evaluación (mini-rúbrica)
- Correctitud: El contexto se consume correctamente y el estado se actualiza al hacer clic.
- Validación: El hook
useAuthlanza un error si se usa fuera del proveedor. - Separación: El contexto está en un archivo separado y el proveedor envuelve la aplicación.
- Funcionalidad: El botón cambia su texto y comportamiento según el estado de autenticación.
- Buenas prácticas: No hay renders innecesarios (verifica con console.log en el cuerpo del componente).
- Usa
createContextsin valor por defecto o connullpara forzar el uso del proveedor. - Para simular un inicio de sesión, puedes usar un objeto usuario fijo como
{ name: 'Usuario', email: '[email protected]' }. - Recuerda envolver la aplicación con
AuthProvideren el archivoindex.jsoApp.js.