¿Qué es la Context API?
La Context API es la forma nativa de React para pasar datos a través del árbol de componentes sin tener que pasar props por cada nivel. Resuelve un problema común y específico: un valor que muchos componentes necesitan, pero que se encuentra muy por encima de ellos en el árbol.
El ejemplo clásico es el manejo de temas (theming). Un Button situado en lo profundo del árbol necesita el tema actual, pero cada componente entre la raíz y el botón tendría que aceptar y reenviar una prop theme. A esto se le llama prop drilling, y Context lo elimina. Proporcionas el valor una sola vez y cualquier descendiente puede leerlo directamente.
Creación y provisión de contexto
El contexto consta de tres piezas: un objeto, un proveedor y un hook de consumo.
// AuthContext.jsx
import { createContext, useContext, useMemo, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(
() => ({
user,
login: (nextUser) => setUser(nextUser),
logout: () => setUser(null),
}),
[user],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
createContext define el contexto y un valor predeterminado. El proveedor suministra el valor actual a todo lo que se encuentre debajo de él. El hook personalizado lee el valor y devuelve un error claro si el componente se utiliza fuera del proveedor.
Consumiendo el contexto
Cualquier descendiente puede leer el valor, sin importar qué tan profundo se encuentre.
// Header.jsx
import { useAuth } from "./AuthContext";
export function Header() {
const { user, logout } = useAuth();
return (
<header>
<span>{user ? `Hi, ${user.name}` : "Guest"}</span>
{user && <button onClick={logout}>Sign out</button>}
</header>
);
}
El Header nunca recibió una prop de App. Accedió directamente al árbol para obtener el valor que necesitaba, lo que evita que los componentes intermedios tengan conocimiento de datos que no utilizan.
El contexto no es estado
Esta distinción suele confundir a muchos. El contexto no mantiene el estado ni gestiona las actualizaciones; lo que hace es transportar un valor. El estado en sí reside en useState o useReducer, y el provider lo distribuye hacia abajo.
Es por eso que un patrón común es combinar el contexto con un reducer:
// CartContext.jsx
import { createContext, useContext, useReducer } from "react";
const CartContext = createContext(null);
function reducer(state, action) {
switch (action.type) {
case "add":
return [...state, action.item];
case "remove":
return state.filter((item) => item.id !== action.id);
default:
return state;
}
}
export function CartProvider({ children }) {
const [items, dispatch] = useReducer(reducer, []);
return (
<CartContext.Provider value={{ items, dispatch }}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => useContext(CartContext);
El reducer mantiene las actualizaciones predecibles y el contexto hace que estén disponibles en cualquier lugar sin necesidad de recurrir al prop drilling.
Rendimiento: la verdadera limitación
Cuando el valor de un provider cambia, todos los componentes que consumen ese contexto se vuelven a renderizar. No existen los selectores, por lo que no puedes suscribirte solo a una parte del valor. Hay dos hábitos que permiten mantener esto bajo control.
Memoriza el valor. Un objeto literal crea una nueva referencia en cada renderizado, lo que significa que cada consumidor se re-renderiza incluso cuando nada ha cambiado.
// memoised
const value = useMemo(() => ({ user, logout }), [user]);
Divide los contextos. Coloca el estado y las acciones en contextos separados, o divídelos por funcionalidad, para que un componente que solo necesita ejecutar una acción no se re-renderice cuando el estado cambie.
// split.jsx
const CartStateContext = createContext(null);
const CartActionsContext = createContext(null);
Los componentes que solo llaman a acciones se suscriben al contexto de acciones, el cual nunca cambia su identidad, por lo que nunca se re-renderizan debido a una actualización de estado. Este sencillo truco resuelve la mayoría de las quejas de rendimiento relacionadas con el contexto.
Cuándo usar Context y cuándo no
Context es la herramienta adecuada para:
- Temas y modo de color.
- El usuario autenticado y la sesión.
- Localización y traducciones.
- Un router o una configuración pequeña y estable.
Recurre a un store dedicado cuando tengas:
- Actualizaciones frecuentes, como escritura, arrastre (dragging) o animaciones.
- Un árbol de estado extenso donde los componentes necesiten selectores.
- Necesidad de middleware, persistencia o devtools de viaje en el tiempo (time-travel).
- Estado del servidor que requiera almacenamiento en caché (caching) y refetching.
Librerías como Zustand y Redux Toolkit añaden selectores para que los componentes se suscriban a fragmentos (slices) en lugar de a todo el valor. Para los datos del servidor, TanStack Query gestiona el caching por completo.
Mejores prácticas
- Envuelve
useContexten un custom hook y verifica que el provider esté presente. - Memoriza los valores del provider con
useMemo. - Divide los contextos según su responsabilidad, especialmente separando el estado de las acciones.
- Mantén los valores del contexto pequeños y enfocados.
- Usa
useReducercuando las actualizaciones sean complejas o dependan del estado anterior. - No utilices el contexto para actualizaciones de alta frecuencia; utiliza un store basado en selectores.
- Define valores predeterminados coherentes para que los componentes puedan renderizarse fuera de un provider cuando sea apropiado.
Errores comunes
- Tratar el context como un gestor de estado y esperar que tenga selectors.
- Pasar un objeto inline como valor del provider, provocando que todo se vuelva a renderizar.
- Colocar datos no relacionados en un único context gigante.
- Olvidar el valor por defecto y provocar un crash en
undefined. - Usar context para valores que solo necesitan uno o dos componentes.
- Esperar que el context optimice los re-renders automáticamente.
Próximos pasos
El Context es la base para compartir el estado en React, y conocer sus límites te indicará cuándo es momento de pasar al siguiente nivel. Compáralo con Redux Toolkit para un estado predecible a gran escala y con Zustand para un store de selectores ligero; luego, gestiona los datos del servidor con TanStack Query. Para Vue, el concepto equivalente se explica en la guía de Pinia.