🚀 ¡Las clases del Bootcamp vuelven el 7 de enero con Node.js! Cargando...

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?

  1. Encapsula useContext: Ya no necesitas importarlo manualmente
  2. Valida el uso correcto: Si intentas usar useAuth fuera del Provider, obtienes un error claro
  3. Mejora el autocompletado: El IDE puede inferir mejor los tipos
  4. 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

AspectouseContext directoCustom Hook
Imports necesarios2 (useContext + Context)1 (useAuth)
Validación❌ No✅ Sí
Mensaje de errorCrípticoClaro y útil
AutocompletadoRegularExcelente
AcoplamientoAltoBajo
Facilidad de refactoringDifícilFácil
Código repetitivoMuchoMí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:

  1. El problema con useContext directo: Código repetitivo y sin validación
  2. Cómo crear un custom hook: Encapsular useContext en una función reutilizable
  3. Validación automática: Detectar uso incorrecto del contexto con mensajes claros
  4. Ventajas del patrón: Menos código, mejor DX, más fácil de mantener
  5. 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.