Optimizando el código del Contexto
En la clase anterior vimos cómo implementar React Context para gestionar estado global. Ahora vamos a mejorar tanto el rendimiento como la experiencia de desarrollo aplicando un patrón profesional: crear un custom hook que encapsule toda la lógica de consumo del contexto.
El problema: demasiado useContext por todos lados
Actualmente, cada componente que necesita información del contexto hace algo así:
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
function Header() {
const { isLogin, login, logout } = useContext(AuthContext)
// ...
}
Y lo mismo en cada componente:
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
function JobDetailPage() {
const { isLogin } = useContext(AuthContext)
// ...
}
¿Cuál es el problema?
PROBLEMAS CON useContext DIRECTO
─────────────────────────────────
❌ Repetición de código
→ Importar useContext + AuthContext en cada archivo
❌ Acoplamiento a la implementación
→ Todos los componentes conocen el nombre interno del contexto
❌ Sin validación
→ Si usas el contexto fuera del Provider, obtienes undefined
❌ Peor autocompletado
→ El IDE no puede inferir tan bien los tipos
❌ Difícil de refactorizar
→ Cambiar el nombre del contexto requiere actualizar N archivos
Esto funciona, pero es repetitivo, ruidoso y expone la implementación interna del contexto a todos los componentes.
La solución: crear un custom hook
La mejor práctica es crear un custom hook que centralice toda la lógica de acceso al contexto.
Paso 1: Crear el custom hook
Vamos a authContext.jsx y añadimos un nuevo hook personalizado:
import { createContext, useContext, useState } from 'react'
export const AuthContext = createContext()
// ✨ Custom hook para consumir el contexto
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth debe usarse dentro de un AuthProvider')
}
return context
}
export function AuthProvider({ children }) {
const [isLogin, setIsLogin] = useState(false)
function login() {
setIsLogin(true)
}
function logout() {
setIsLogin(false)
}
const value = { isLogin, login, logout }
return <AuthContext value={value}>{children}</AuthContext>
}
Anatomía del custom hook
useAuth()
↓
[1] Llama a useContext(AuthContext)
↓
[2] Valida que context !== undefined
↓
├─ Si es undefined → Error claro ❌
| "useAuth debe usarse dentro de un AuthProvider"
|
└─ Si existe → Devuelve el contexto ✅
{ isLogin, login, logout }
¿Qué hace este hook?
- Encapsula
useContext: Ya no necesitas importarlo manualmente - Valida el uso correcto: Si intentas usar
useAuthfuera del Provider, obtienes un error claro - Mejora el autocompletado: El IDE puede inferir mejor los tipos
- Centraliza la lógica: Un solo punto de entrada al contexto
Paso 2: Usar el custom hook en los componentes
Ahora, en lugar de importar useContext y AuthContext, simplemente importamos nuestro hook:
Antes (con useContext directo)
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
function Header() {
const { isLogin, login, logout } = useContext(AuthContext)
return <header>{/* ... */}</header>
}
Después (con custom hook)
import { useAuth } from '../context/authContext'
function Header() {
const { isLogin, login, logout } = useAuth()
return <header>{/* ... */}</header>
}
Comparación visual
ANTES DESPUÉS
───────────────── ─────────────────
import { useContext } import { useAuth }
import { AuthContext }
useContext(AuthContext) useAuth()
2 imports 1 import
Expone implementación Oculta implementación
Sin validación Con validación
Ventajas del custom hook
1. Menos código repetitivo
Antes:
// Componente A
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
const auth = useContext(AuthContext)
// Componente B
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
const auth = useContext(AuthContext)
// Componente C
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
const auth = useContext(AuthContext)
Después:
// Componente A
import { useAuth } from '../context/authContext'
const auth = useAuth()
// Componente B
import { useAuth } from '../context/authContext'
const auth = useAuth()
// Componente C
import { useAuth } from '../context/authContext'
const auth = useAuth()
2. Validación automática
Si intentas usar el hook fuera del Provider:
function ComponenteFueraDelProvider() {
const auth = useAuth() // ❌ Error claro y útil
// Error: useAuth debe usarse dentro de un AuthProvider
}
Sin el custom hook, obtendrías:
const auth = useContext(AuthContext) // undefined
auth.login() // ❌ Cannot read property 'login' of undefined
3. Mejor experiencia de desarrollo
VENTAJAS PARA EL DESARROLLADOR
──────────────────────────────
✅ Autocompletado mejorado
→ El IDE sugiere { isLogin, login, logout }
✅ Errores más claros
→ Mensaje específico si lo usas mal
✅ Menos imports
→ Solo necesitas importar useAuth
✅ Abstracción limpia
→ No necesitas saber que usa Context internamente
✅ Fácil de refactorizar
→ Cambiar la implementación no afecta a los componentes
4. Facilita el refactoring
Si en el futuro decides cambiar de Context a Zustand, Redux o cualquier otra solución, solo tienes que modificar el hook:
// Cambiar de Context a Zustand
export function useAuth() {
// Antes: return useContext(AuthContext)
// Después: return useAuthStore()
return useAuthStore()
}
Los componentes que usan useAuth() no necesitan cambiar nada. ✨
Aplicando el patrón en toda la app
Ahora actualizamos todos los componentes que consumen el contexto:
En el Header
import { useAuth } from '../context/authContext'
export function Header() {
const { isLogin, login, logout } = useAuth()
return (
<header>
<Logo />
<Nav />
{isLogin ? (
<button onClick={logout}>Cerrar sesión</button>
) : (
<button onClick={login}>Iniciar sesión</button>
)}
</header>
)
}
En JobDetailPage
import { useAuth } from '../context/authContext'
export function JobDetailPage() {
const { isLogin } = useAuth()
return <div>{isLogin ? <ApplyButton /> : <p>Inicia sesión para aplicar</p>}</div>
}
En cualquier otro componente
import { useAuth } from '../context/authContext'
export function UserProfile() {
const { isLogin, logout } = useAuth()
if (!isLogin) return <Redirect to="/login" />
return <div>Perfil de usuario</div>
}
Patrón completo: Context + Custom Hook
Aquí está el patrón completo que deberías seguir siempre que crees un contexto:
import { createContext, useContext, useState } from 'react'
// 1. Crear el contexto
export const AuthContext = createContext()
// 2. Crear el custom hook con validación
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth debe usarse dentro de un AuthProvider')
}
return context
}
// 3. Crear el Provider
export function AuthProvider({ children }) {
const [isLogin, setIsLogin] = useState(false)
const login = () => setIsLogin(true)
const logout = () => setIsLogin(false)
const value = { isLogin, login, logout }
return <AuthContext value={value}>{children}</AuthContext>
}
Estructura de archivos recomendada
/src
/context
authContext.jsx ← Context + Provider + Custom Hook
/components
Header.jsx ← Usa useAuth()
JobDetailPage.jsx ← Usa useAuth()
UserProfile.jsx ← Usa useAuth()
Comparación final: Antes vs Después
Código en los componentes
ANTES (sin custom hook):
// 😕 Muchos imports
import { useContext } from 'react'
import { AuthContext } from '../context/authContext'
function MyComponent() {
// 😕 Sintaxis verbosa
const { isLogin } = useContext(AuthContext)
// 😕 Sin validación
// Si AuthContext es undefined → error críptico
return <div>{isLogin ? 'Logged' : 'Guest'}</div>
}
DESPUÉS (con custom hook):
// ✅ Un solo import
import { useAuth } from '../context/authContext'
function MyComponent() {
// ✅ Sintaxis limpia
const { isLogin } = useAuth()
// ✅ Con validación automática
// Si se usa mal → error claro
return <div>{isLogin ? 'Logged' : 'Guest'}</div>
}
Tabla comparativa
| Aspecto | useContext directo | Custom Hook |
|---|---|---|
| Imports necesarios | 2 (useContext + Context) | 1 (useAuth) |
| Validación | ❌ No | ✅ Sí |
| Mensaje de error | Críptico | Claro y útil |
| Autocompletado | Regular | Excelente |
| Acoplamiento | Alto | Bajo |
| Facilidad de refactoring | Difícil | Fácil |
| Código repetitivo | Mucho | Mínimo |
Buenas prácticas con custom hooks de contexto
1. Nombra el hook según el contexto
// ✅ Bueno
export function useAuth() { ... }
export function useTheme() { ... }
export function useCart() { ... }
// ❌ Malo
export function useContext() { ... }
export function useData() { ... }
export function useStuff() { ... }
2. Siempre valida el contexto
// ✅ Bueno
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth debe usarse dentro de un AuthProvider')
}
return context
}
// ❌ Malo (sin validación)
export function useAuth() {
return useContext(AuthContext) // Puede ser undefined
}
3. Exporta solo lo necesario
// ✅ Bueno
export function useAuth() { ... }
export function AuthProvider({ children }) { ... }
// No exportes AuthContext
// ❌ Malo
export const AuthContext = createContext() // Expone implementación
export function useAuth() { ... }
export function AuthProvider({ children }) { ... }
4. Documenta el hook
/**
* Hook para acceder al contexto de autenticación
*
* @returns {Object} Objeto con isLogin, login y logout
* @throws {Error} Si se usa fuera de AuthProvider
*
* @example
* function MyComponent() {
* const { isLogin, login, logout } = useAuth()
* return <button onClick={login}>Login</button>
* }
*/
export function useAuth() {
// ...
}
Resumen de la clase
En esta clase has aprendido:
- El problema con
useContextdirecto: Código repetitivo y sin validación - Cómo crear un custom hook: Encapsular
useContexten una función reutilizable - Validación automática: Detectar uso incorrecto del contexto con mensajes claros
- Ventajas del patrón: Menos código, mejor DX, más fácil de mantener
- Buenas prácticas: Nombrado, validación, exports y documentación
Conclusión
Crear un custom hook para cada contexto es una best practice profesional que:
- ✅ Reduce código repetitivo
- ✅ Mejora la experiencia de desarrollo
- ✅ Facilita el mantenimiento y refactoring
- ✅ Añade validaciones útiles
- ✅ Oculta detalles de implementación
Regla de oro: Siempre que crees un Context, crea también su custom hook. Tu yo del futuro te lo agradecerá.
En la siguiente clase veremos cómo llevar la gestión de estado global al siguiente nivel con Zustand, una librería que soluciona las limitaciones de rendimiento de Context manteniendo una API simple y elegante.