Context en React: diseño de providers y rendimiento | Nicolás Garzón
Context transporta un valor hacia descendientes sin pasar props por cada nivel. Cada consumidor se actualiza cuando cambia el valor observado.
TypeScript
Copiar const AuthContext = createContext < AuthContextValue | null > ( null ) ; Un hook de acceso centraliza validación:
TypeScript
Copiar function useAuth ( ) {
const value = useContext ( AuthContext) ;
if ( ! value) throw new Error ( "useAuth requiere AuthProvider" ) ;
return value;
} Un solo contexto enorme provoca actualizaciones amplias. Divide valores con ritmos de cambio diferentes:
Datos del usuario.
Acciones de autenticación.
Tema.
Configuración estática.
TypeScript
Copiar < AuthContext value= { { user, logout } } > El objeto se crea en cada render. Esto no siempre es un problema, pero en árboles grandes puede justificar separar providers o estabilizar valores después de medir.
Antes de usar Context, revisa si children, slots o props directas mantienen dependencias más claras.
Datos remotos con caché, revalidación y deduplicación suelen pertenecer a una herramienta de server state, no a Context manual.
Meter cada estado global en un único provider.
Usar Context para evitar diseñar props.
Actualizar con mucha frecuencia un contexto consumido por toda la app.
Ocultar dependencias de componentes.
Context resuelve distribución, no arquitectura completa de estado. Diseña providers por responsabilidad y frecuencia de cambio.
Un provider debería representar una dependencia o dominio reconocible:
Texto
Copiar ThemeProvider → preferencia visual
SessionProvider → identidad actual
CartProvider → estado de carritoUn AppContext con usuario, tema, notificaciones, carrito y modales mezcla ritmos de cambio y obliga a consumidores a depender de una superficie enorme.
No todo provider pertenece al root:
TypeScript
Copiar function CheckoutRoute ( ) {
return (
< CheckoutProvider>
< CheckoutPage / >
< / CheckoutProvider>
) ;
} Colocarlo cerca de la feature:
Limita consumidores.
Define su ciclo de vida.
Reinicia state al salir cuando esa es la semántica.
Reduce dependencias globales.
Un provider global es apropiado cuando su valor realmente pertenece a toda la aplicación, como locale o sesión.
TypeScript
Copiar const CartStateContext = createContext < CartState | null > ( null ) ;
const CartDispatchContext = createContext < React. Dispatch< CartAction> | null > ( null ) ; dispatch suele conservar identidad. Un botón que solo despacha puede consumir acciones sin volver a renderizar por cada cambio de items.
Esto no crea selectores para partes arbitrarias del state; los consumidores del contexto de datos siguen actualizándose cuando cambia su value.
TypeScript
Copiar const value = { user, logout } ; El objeto cambia por referencia cada vez que el provider renderiza. Antes de memoizar, pregunta:
¿El provider renderiza por state no relacionado?
¿Los consumers son numerosos o costosos?
¿logout cambia de identidad?
¿El problema está medido?
TypeScript
Copiar const value = useMemo ( ( ) => ( { user, logout } ) , [ user, logout] ) ; Memoizar conserva referencia, pero no corrige un provider demasiado amplio ni evita cambios cuando una dependencia real cambia.
La API base de Context notifica a todos los consumidores del value. Para actualizaciones frecuentes y parciales evalúa:
Dividir contextos.
Mover state más cerca.
Pasar elementos por composición.
Store externa con selectores.
Librería que implemente context selectors bajo un contrato claro.
No construyas una store casera dentro de Context solo para evitar una dependencia sin comprender concurrencia y suscripciones.
No todo valor debe ser state:
TypeScript
Copiar const AnalyticsContext = createContext < AnalyticsClient | null > ( null ) ; Una rama puede recibir un cliente estable para registrar eventos. Esto facilita tests y evita imports globales. El provider distribuye la dependencia; no necesita actualizarse.
Para un provider obligatorio, usa null y falla con un mensaje útil:
TypeScript
Copiar function useCart ( ) {
const value = useContext ( CartContext) ;
if ( value === null ) {
throw new Error ( "useCart must be used within CartProvider" ) ;
}
return value;
} Un objeto default con funciones vacías puede ocultar una configuración rota y hacer que acciones no produzcan ningún resultado.
Crea un wrapper de prueba:
TypeScript
Copiar function renderWithProviders ( ui: React. ReactNode) {
return render (
< ThemeContext value= "dark" >
< SessionContext value= { testSession} > { ui} < / SessionContext>
< / ThemeContext> ,
) ;
} Prueba comportamiento del consumidor, además del error cuando falta un provider obligatorio.
No distribuyas posición del mouse completa a toda la app mediante Context. Cada movimiento puede actualizar numerosos consumers. Mantén el dato cerca del componente visual, usa refs para trabajo no renderizable o una store selectiva si muchas regiones realmente lo necesitan.
Identifica qué provider cambió.
Compara el value anterior y nuevo.
Revisa si el cambio es real o solo de referencia.
Lista qué consumers dependen de cada parte.
Divide por responsabilidad antes de memoizar masivamente.
Perfila el resultado.
Un provider define una frontera de dependencia y ciclo de vida.
Context global no es el valor predeterminado.
Divide dominios y ritmos de cambio.
Memoizar value solo ayuda cuando la referencia innecesaria es el problema.
Para suscripciones parciales frecuentes, considera una store con selectores.
¿Qué ventaja tiene colocar un provider dentro de una ruta?
¿Por qué separar state y dispatch puede reducir dependencias?
¿Qué no resuelve useMemo sobre el value?
¿Cuándo Context sirve aunque el valor nunca cambie?
Ver respuestas
Limita alcance y alinea el ciclo de vida con la feature.
Quien solo despacha no consume cada cambio del state.
Un dominio demasiado amplio o cambios reales de sus dependencias.
Para inyectar configuración o servicios estables a una rama.
Reglas de los Hooks explica las restricciones que permiten a React asociar cada llamada con su memoria correcta.