Crear contextos, providers y combinar con useReducer.
Context básico
createContextcrea un contenedor de datos.Providerenvuelve la app y comparte el estado.useContextconsume la información.- Conviene definir un valor inicial y un custom hook para simplificar el consumo.
- Si el contexto es obligatorio, lanzar error si se usa fuera del Provider.
¿Cuándo usar Context?
- Para datos globales reales: tema, auth, idioma, permisos, carrito.
- Evita usarlo para estados muy locales; puede generar renders innecesarios.
- Si solo dos componentes comparten datos, props puede ser mas simple.
Composición de providers
- Se pueden anidar multiples providers en un componente
AppProviders. - Ayuda a mantener
main.jsxlimpio y ordenado. - Prioriza providers con menos cambios arriba para evitar renders masivos.
Context para autenticación
- Centraliza login, logout y usuario actual.
- Permite proteger rutas con la misma fuente de verdad.
- Guarda el estado de "cargando" mientras valida sesion.
- Puede incluir roles/permisos para habilitar secciones.
Context + useReducer
- Útil para estados complejos como un carrito.
- La lógica se organiza por acciones.
- Define un
initialStatey acciones claras (ADD, REMOVE, UPDATE). - Mantener el reducer puro facilita testing y depuracion.
Buenas prácticas y rendimiento
- Separa contextos por dominio para evitar renders globales.
- Memoiza el
valuedel Provider cuando depende de objetos. - Evita pasar funciones inline que cambien en cada render.
El criterio: ¿Context o props?
"Usa Context" es la respuesta que la IA da a casi cualquier problema de datos compartidos, porque le evita pensar el árbol de componentes. El criterio real:
| Situación | Herramienta |
|---|---|
| El dato tiene un destino conocido, aunque esté hondo | Composición con children (lo viste en props) |
| El dato es transversal y lo consumen muchos puntos imprevisibles | Context |
| Solo dos componentes hermanos lo comparten | Estado en el padre común + props |
La señal práctica de que llegó el momento de Context: cada vez que agregas un consumidor nuevo tienes que volver a cablear componentes intermedios que no usan el dato, y eso pasa seguido. Los casos legítimos son pocos y conocidos: sesión del usuario, tema visual, idioma, carrito.
Context no es "estado global mágico": es un cable para evitar el prop drilling. No reemplaza al estado ni te da persistencia.
La trampa del value que siempre cambia
Este es el error de rendimiento más común con Context, y es sutil:
jsx1// ❌ objeto NUEVO en cada render del Provider 2<AuthContext.Provider value={{ usuario, login, logout }}>
Cada render del Provider crea un objeto nuevo. Para React eso es un valor distinto, así que todos los consumidores se re-renderizan, aunque usuario no haya cambiado. En una app chica no se nota; con un contexto que consumen 20 componentes, sí.
jsx1// ✅ la referencia solo cambia cuando cambia lo que importa 2const value = useMemo( 3 () => ({ usuario, login, logout }), 4 [usuario, login, logout] 5); 6 7<AuthContext.Provider value={value}>
(Para que login y logout sean estables a su vez, se declaran con useCallback dentro del Provider.)
El custom hook que blinda el consumo
En vez de que cada componente haga useContext(AuthContext), se expone un hook propio que además falla ruidosamente si alguien lo usa fuera del Provider:
jsx1export function useAuth() { 2 const ctx = useContext(AuthContext); 3 if (ctx === undefined) { 4 throw new Error("useAuth debe usarse dentro de <AuthProvider>"); 5 } 6 return ctx; 7}
Sin esa verificación, usarlo fuera del Provider devuelve undefined y truena después, en otra línea, con un mensaje que no dice nada. Un error claro en el momento correcto vale más que diez horas de depuración — es el mismo principio del "error silencioso" de la Auditoría IA #2.
Ejemplos de Código
3 ejemplos
Context básico
1import { createContext, useContext, useState } from "react";
2
3const ThemeContext = createContext();
4
5export function ThemeProvider({ children }) {
6 const [theme, setTheme] = useState("light");
7 const toggleTheme = () => setTheme((t) => (t === "light" ? "dark" : "light"));
8
9 return (
10 <ThemeContext.Provider value={{ theme, toggleTheme }}>
11 {children}
12 </ThemeContext.Provider>
13 );
14}
15
16export function useTheme() {
17 return useContext(ThemeContext);
18}Auth con Context
1import { createContext, useContext, useEffect, useState } from "react";
2
3const AuthContext = createContext();
4
5export function AuthProvider({ children }) {
6 const [user, setUser] = useState(null);
7
8 useEffect(() => {
9 const saved = localStorage.getItem("user");
10 if (saved) setUser(JSON.parse(saved));
11 }, []);
12
13 const login = (userData) => {
14 setUser(userData);
15 localStorage.setItem("user", JSON.stringify(userData));
16 };
17
18 const logout = () => {
19 setUser(null);
20 localStorage.removeItem("user");
21 };
22
23 return (
24 <AuthContext.Provider value={{ user, login, logout }}>
25 {children}
26 </AuthContext.Provider>
27 );
28}
29
30export function useAuth() {
31 return useContext(AuthContext);
32}Reducer global
1import { createContext, useContext, useReducer } from "react";
2
3const CartContext = createContext();
4
5function reducer(state, action) {
6 switch (action.type) {
7 case "ADD":
8 return { ...state, items: [...state.items, action.payload] };
9 case "CLEAR":
10 return { ...state, items: [] };
11 default:
12 return state;
13 }
14}
15
16export function CartProvider({ children }) {
17 const [state, dispatch] = useReducer(reducer, { items: [] });
18
19 return (
20 <CartContext.Provider value={{ state, dispatch }}>
21 {children}
22 </CartContext.Provider>
23 );
24}
25
26export function useCart() {
27 return useContext(CartContext);
28}Recursos
3 recursos disponibles
¡Hora de Practicar!
Práctica guiada - Theme + Auth
Práctica
Implementar tema claro/oscuro y login con Context.
Desafío de Código
Ejercicios - Carrito global
Ejercicios
(1) Agregar items, (2) eliminar items, (3) total acumulado.
Documentación Oficial
Clase 25 de 33 en la ruta Desarrollo web