Hook useContext
Lee y suscríbete a contexto desde cualquier componente sin prop drilling.
Receta
Tarjeta de receta de referencia rápida — lista para copiar y pegar.
// 1. Crea el contexto
const ThemeContext = createContext<Theme>("light");
// 2. Proporciónalo
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>
// 3. Consúmelo
const theme = useContext(ThemeContext);Cuándo usarlo: Necesitas compartir valores (tema, autenticación, configuración regional) en muchos componentes sin pasar props en cada nivel.
Ejemplo Funcional
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
const ThemeContext = createContext<Theme>("light");
const ThemeToggleContext = createContext<() => void>(() => {});
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={theme}>
<ThemeToggleContext.Provider value={toggle}>
{children}
</ThemeToggleContext.Provider>
</ThemeContext.Provider>
);
}
export function ThemeDisplay() {
const theme = useContext(ThemeContext);
const toggle = useContext(ThemeToggleContext);
return (
<div className={theme === "dark" ? "bg-gray-900 text-white p-4" : "bg-white text-black p-4"}>
<p>Tema actual: {theme}</p>
<button onClick={toggle} className="mt-2 px-3 py-1 border rounded">
Alternar Tema
</button>
</div>
);
}Lo que esto demuestra:
- Crear y proporcionar contexto con
createContextyProvider - Dividir el valor y el actualizador en contextos separados para evitar re-renderizados innecesarios
- Consumir contexto con
useContexten un componente profundamente anidado - Contexto seguro de tipo con genéricos explícitos
Inmersión Profunda
Cómo Funciona
useContextencuentra elProvidermás cercano por encima del componente que llama en el árbol- Cuando cambia el
valuedel proveedor, cada componente que consume ese contexto se re-renderiza - Si no se encuentra ningún proveedor, se utiliza el valor predeterminado pasado a
createContext - El contexto utiliza igualdad de referencia (
Object.is) para detectar cambios — una nueva referencia de objeto dispara re-renderizados incluso si el contenido es idéntico
Parámetros y Valores de Retorno
| Parámetro | Tipo | Descripción |
|---|---|---|
context | Context<T> | El objeto de contexto creado por createContext |
| Retorno | Tipo | Descripción |
|---|---|---|
value | T | El valor actual del contexto del proveedor más cercano |
Variaciones
Contexto con un hook personalizado (patrón recomendado):
const AuthContext = createContext<AuthState | null>(null);
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth debe usarse dentro de un AuthProvider");
return ctx;
}Múltiples contextos compuestos:
export function AppProviders({ children }: { children: ReactNode }) {
return (
<AuthProvider>
<ThemeProvider>
<LocaleProvider>
{children}
</LocaleProvider>
</ThemeProvider>
</AuthProvider>
);
}Contexto con reductor para estado complejo:
const DispatchContext = createContext<Dispatch<Action>>(() => {});
const StateContext = createContext<AppState>(initialState);Notas de TypeScript
// Siempre tipifica tu contexto — evita `any`
const UserContext = createContext<User | null>(null);
// Usa un hook personalizado para estrechar el tipo
export function useUser(): User {
const user = useContext(UserContext);
if (!user) throw new Error("useUser requiere un UserProvider");
return user; // estrechado a User, nunca null
}Trampas
-
Avalancha de re-renderizado — Pasar un literal de objeto nuevo como
valueen cada renderizado hace que todos los consumidores se re-renderizen. Solución: Memoiza el valor conuseMemoo divide en contextos separados. -
Proveedor Faltante — Olvidar el
Providerdevuelve silenciosamente el valor predeterminado, que puede serundefined. Solución: Usa un hook personalizado que lance una excepción si el contexto esnull. -
Uso excesivo de contexto para actualizaciones de alta frecuencia — El contexto no está optimizado para valores que cambian en cada pulsación de tecla o fotograma de animación. Solución: Usa
useSyncExternalStore, Zustand o Jotai para estado compartido de alta frecuencia. -
Confusión de Valor Predeterminado — El valor predeterminado en
createContext(defaultValue)solo se usa cuando no hay proveedor, no como estado inicial para el proveedor. Solución: Siempre envuelve los componentes consumidores en el proveedor apropiado.
Alternativas
| Alternativa | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
| Prop drilling | Solo 1-2 niveles de profundidad y pocos consumidores | Muchos niveles o muchos consumidores |
| Zustand / Jotai | Actualizaciones de alta frecuencia o estado compartido complejo | Valores simples e infrecuentes como tema o configuración regional |
| Composición de componentes | Los hijos pueden ser pasados como props para evitar que componentes intermedios necesiten los datos | Los datos se necesitan en muchos niveles arbitrarios |
use(Context) (React 19) | Quieres leer contexto condicionalmente o en bucles | Necesitas soportar React 18 o versiones anteriores |
¿Por qué no siempre usar Zustand? El contexto está integrado, no requiere ninguna dependencia y es la herramienta correcta para valores de baja frecuencia limitados al árbol como tema, autenticación o configuración regional.
Ejemplo del Mundo Real
De una aplicación SaaS de Next.js 15 / React 19 en producción (SystemsArchitect.io).
// Ejemplo de producción: ToastProvider con createContext y hook de guarda
// Archivo: src/components/toast-provider.tsx
'use client';
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
interface Toast {
id: string;
message: string;
type: 'success' | 'error' | 'info';
}
interface ToastContextValue {
toasts: Toast[];
showToast: (message: string, type?: Toast['type']) => void;
dismissToast: (id: string) => void;
}
const ToastContext = createContext<ToastContextValue | null>(null);
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const showToast = useCallback((message: string, type: Toast['type'] = 'info') => {
const id = crypto.randomUUID();
setToasts((prev) => [...prev, { id, message, type }]);
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, 5000);
}, []);
const dismissToast = useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, showToast, dismissToast }}>
{children}
<div className="fixed bottom-4 right-4 space-y-2 z-50">
{toasts.map((toast) => (
<div key={toast.id} className="rounded bg-gray-900 text-white px-4 py-2 shadow">
{toast.message}
<button onClick={() => dismissToast(toast.id)} className="ml-2">x</button>
</div>
))}
</div>
</ToastContext.Provider>
);
}
// Hook de guarda: lanza una excepción si se usa fuera del proveedor
export function useToast(): ToastContextValue {
const ctx = useContext(ToastContext);
if (!ctx) {
throw new Error('useToast debe usarse dentro de un ToastProvider');
}
return ctx;
}Lo que esto demuestra en producción:
createContext<ToastContextValue | null>(null)usanullcomo valor predeterminado para que el hook de guarda pueda detectar cuándo se usa fuera de un proveedor. Usar un valor predeterminado real fallaría silenciosamente en lugar de lanzar un error útil.useCallbackenshowToastydismissToastmantiene estables estas referencias de función en todos los renderizados. Sin él, cualquier componente que consumeuseToast()se re-renderizaría en cada cambio de estado de toast porque el objeto de valor de contexto contendría nuevas referencias de función.- Se utilizan actualizaciones funcionales de estado (
setToasts((prev) => ...)) en lugar de leertoastsdirectamente. Esto evita agregartoastsal array de dependencias deuseCallback, lo que rompería la referencia estable. - El
setTimeoutpara descartar automáticamente captura eliden su cierre y utiliza un actualizador funcional para filtrar. Esto evita errores de cierre obsoleto donde el array detoastsha cambiado cuando el tiempo de espera se activa. - El hook de guarda
useToast()estrecha el tipo deToastContextValue | nullaToastContextValue, por lo que los consumidores nunca necesitan manejarnull. Elthrowasegura un mensaje de error claro durante el desarrollo si el proveedor falta.
Preguntas Frecuentes
¿Por qué debería dividir el valor y el actualizador en contextos separados?
- Cuando un único contexto contiene tanto el valor de estado como su función actualizadora, cada consumidor se re-renderiza cada vez que cambia el valor.
- Los componentes que solo llaman al actualizador (como un botón) no necesitan el valor, pero aún así se re-renderizen.
- Dividir en dos contextos permite que los consumidores solo actualizadores eviten re-renderizados innecesarios.
¿Qué sucede si uso useContext sin un Proveedor coincidente?
- React devuelve el valor predeterminado pasado a
createContext(defaultValue). - Si el valor predeterminado es
undefinedonull, tu componente puede fallar silenciosamente. - Usa un hook de guarda que lance un error cuando el contexto es
nullpara capturar esto durante el desarrollo.
¿Cómo creo un hook de guarda seguro de tipo para contexto en TypeScript?
const MyContext = createContext<MyType | null>(null);
export function useMyContext(): MyType {
const ctx = useContext(MyContext);
if (!ctx) throw new Error("useMyContext debe usarse dentro de un Proveedor");
return ctx; // estrechado a MyType, nunca null
}Trampa: ¿Por qué se re-renderizen todos mis consumidores de contexto incluso cuando el valor no ha cambiado realmente?
- El contexto utiliza
Object.ispara detectar cambios. Pasar un literal de objeto nuevo{ theme, toggle }como valor del proveedor crea una nueva referencia en cada renderizado. - Soluciona memoizando el valor con
useMemo:
const value = useMemo(() => ({ theme, toggle }), [theme, toggle]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;¿Puedo usar contexto para actualizaciones de alta frecuencia como escritura o animaciones?
- El contexto no está optimizado para actualizaciones de alta frecuencia porque cada consumidor se re-renderiza en cada cambio.
- Para estado compartido de alta frecuencia, usa
useSyncExternalStore, Zustand o Jotai en su lugar. - El contexto funciona bien para valores de baja frecuencia como tema, autenticación o configuración regional.
¿Cuál es la diferencia entre el valor predeterminado en createContext y la prop value del Proveedor?
- El valor predeterminado en
createContext(default)se usa solo cuando no hay un Proveedor encima del consumidor. - La prop
valuedel Proveedor es el valor real en tiempo de ejecución pasado a los consumidores. - El valor predeterminado no es un "estado inicial" para el Proveedor.
¿Cómo compongo múltiples Proveedores sin anidar JSX profundamente?
function AppProviders({ children }: { children: ReactNode }) {
return (
<AuthProvider>
<ThemeProvider>
<LocaleProvider>
{children}
</LocaleProvider>
</ThemeProvider>
</AuthProvider>
);
}- Alternativamente, crea una utilidad
composeProvidersque reduzca un array de proveedores.
¿Debería usar useContext o el API use(Context) de React 19?
use(Context)es nuevo en React 19 y puede ser llamado dentro de condiciones y bucles, a diferencia deuseContext.useContextes el enfoque estándar si necesitas compatibilidad con React 18.- Ambos leen del Proveedor más cercano; la diferencia es la flexibilidad del sitio de llamada.
Trampa: ¿Por qué envolver useCallback alrededor de funciones actualizadoras de contexto es importante?
- Si la función actualizadora se recrea en cada renderizado, el objeto de valor de contexto cambia de referencia.
- Eso dispara re-renderizados en todos los consumidores, incluso si el estado en sí no ha cambiado.
- Envolver actualizadores en
useCallbackmantiene sus referencias estables en todos los renderizados.
¿Cómo tipifica createContext con TypeScript cuando el contexto puede no tener un Proveedor?
// Usa el patrón null default + guard hook
const UserContext = createContext<User | null>(null);
// El hook de guarda estrecha el tipo
export function useUser(): User {
const user = useContext(UserContext);
if (!user) throw new Error("useUser requiere un UserProvider");
return user;
}Relacionado
- useReducer — empareja con contexto para gestión de estado complejo
- useMemo — memoiza valores de contexto para evitar re-renderizados innecesarios
- use — el
use()de React 19 puede leer contexto condicionalmente - Custom Hooks — envuelve
useContexten un hook personalizado para seguridad de tipo