Guía Completa para Dominar el Hook useContext en React

¿Qué esel Hook useContext?

useContext es un Hook de React que permite suscribirse y consumir datos desde un Context Provider en cualquier nivel del árbol de componentes. Es la solución de React para evitar el “prop drilling” (pasar props a través de múltiples niveles de componentes).

  Características principales

  • 🌍 Compartir estado global
  • ⚡ Acceso a datos desde cualquier componente hijo
  • 🎯 Evita prop drilling
  • 🔄 Actualización automática de componentes

Sintaxis Básica

jsx
// 1. Crear el contexto
const MiContexto = createContext(valorInicial);

// 2. Proveer el contexto
<MiContexto.Provider value={valor}>
{children}
</MiContexto.Provider>

// 3. Consumir el contexto
const valor = useContext(MiContexto);

Ejemplo Completo de Implementación


1. Creación y Configuración del Contexto

jsx
// ThemeContext.js
import { createContext, useState } from 'react';

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');

const toggleTheme = () => {
  setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};

const value = {
  theme,
  toggleTheme
};

return (
  <ThemeContext.Provider value={value}>
    {children}
  </ThemeContext.Provider>
);
}

2. Uso en Componentes

jsx
// App.js
function App() {
return (
  <ThemeProvider>
    <MainLayout />
  </ThemeProvider>
);
}

// MainLayout.js
function MainLayout() {
const { theme } = useContext(ThemeContext);

return (
  <div className={`layout ${theme}`}>
    <Navbar />
    <Content />
    <Footer />
  </div>
);
}

// ThemeToggle.js
function ThemeToggle() {
const { theme, toggleTheme } = useContext(ThemeContext);

return (
  <button onClick={toggleTheme}>
    Cambiar a tema {theme === 'light' ? 'oscuro' : 'claro'}
  </button>
);
}

Casos de Uso Prácticos


1. Gestión de Autenticación

jsx
// AuthContext.js
import { createContext, useState, useContext } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

const login = async (credentials) => {
  setLoading(true);
  try {
    const response = await apiLogin(credentials);
    setUser(response.user);
    return true;
  } catch (error) {
    return false;
  } finally {
    setLoading(false);
  }
};

const logout = () => {
  setUser(null);
};

return (
  <AuthContext.Provider value={{ user, loading, login, logout }}>
    {children}
  </AuthContext.Provider>
);
}

// Hook personalizado para usar el contexto
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
  throw new Error('useAuth debe usarse dentro de un AuthProvider');
}
return context;
}

2. Gestión de Carrito de Compras

jsx
// CartContext.js
const CartContext = createContext();

export function CartProvider({ children }) {
const [items, setItems] = useState([]);

const addItem = (product) => {
  setItems(prevItems => {
    const existingItem = prevItems.find(item => item.id === product.id);
    if (existingItem) {
      return prevItems.map(item =>
        item.id === product.id
          ? { ...item, quantity: item.quantity + 1 }
          : item
      );
    }
    return [...prevItems, { ...product, quantity: 1 }];
  });
};

const removeItem = (productId) => {
  setItems(prevItems => 
    prevItems.filter(item => item.id !== productId)
  );
};

const clearCart = () => {
  setItems([]);
};

const getTotal = () => {
  return items.reduce((total, item) => 
    total + (item.price * item.quantity), 0
  );
};

return (
  <CartContext.Provider value={{
    items,
    addItem,
    removeItem,
    clearCart,
    getTotal
  }}>
    {children}
  </CartContext.Provider>
);
}

3. Gestión de Idiomas (i18n)

jsx
// LanguageContext.js
const LanguageContext = createContext();

const translations = {
es: {
  welcome: 'Bienvenido',
  goodbye: 'Adiós'
},
en: {
  welcome: 'Welcome',
  goodbye: 'Goodbye'
}
};

export function LanguageProvider({ children }) {
const [language, setLanguage] = useState('es');

const translate = (key) => {
  return translations[language][key] || key;
};

return (
  <LanguageContext.Provider value={{
    language,
    setLanguage,
    translate
  }}>
    {children}
  </LanguageContext.Provider>
);
}

Patrones Avanzados


1. Múltiples Contextos

jsx
function App() {
return (
  <AuthProvider>
    <ThemeProvider>
      <LanguageProvider>
        <CartProvider>
          <MainApp />
        </CartProvider>
      </LanguageProvider>
    </ThemeProvider>
  </AuthProvider>
);
}

2. Context con Reducer

jsx
const initialState = { count: 0 };

function reducer(state, action) {
switch (action.type) {
  case 'increment':
    return { count: state.count + 1 };
  case 'decrement':
    return { count: state.count - 1 };
  default:
    throw new Error();
}
}

function CountProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);

return (
  <CountContext.Provider value={{ state, dispatch }}>
    {children}
  </CountContext.Provider>
);
}

Mejores Prácticas 🎯


1. Crear Hooks Personalizados

jsx
// useTheme.js
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
  throw new Error('useTheme debe usarse dentro de un ThemeProvider');
}
return context;
}

2. Separación de Contextos

jsx
// Mejor: Contextos separados para diferentes responsabilidades
const UserContext = createContext();
const ThemeContext = createContext();
const SettingsContext = createContext();

// Peor: Un solo contexto para todo
const AppContext = createContext();

3. Optimización de Renders

jsx
function OptimizedProvider({ children }) {
const [state, setState] = useState({
  theme: 'light',
  user: null
});

const themeValue = useMemo(() => ({
  theme: state.theme,
  setTheme: (theme) => setState(prev => ({ ...prev, theme }))
}), [state.theme]);

const userValue = useMemo(() => ({
  user: state.user,
  setUser: (user) => setState(prev => ({ ...prev, user }))
}), [state.user]);

return (
  <ThemeContext.Provider value={themeValue}>
    <UserContext.Provider value={userValue}>
      {children}
    </UserContext.Provider>
  </ThemeContext.Provider>
);
}

Errores Comunes y Soluciones


1. No Envolver el Provider

jsx
// ❌ Mal: Usar contexto sin provider
function App() {
const theme = useContext(ThemeContext); // Error!
return <div>{theme}</div>;
}

// ✅ Bien: Asegurar que existe un provider
function App() {
return (
  <ThemeProvider>
    <ThemedComponent />
  </ThemeProvider>
);
}

2. Re-renders Innecesarios

jsx
// ❌ Mal: Valor nuevo en cada render
function Provider({ children }) {
return (
  <MyContext.Provider value={{ data: {} }}>
    {children}
  </MyContext.Provider>
);
}

// ✅ Bien: Valor memorizado
function Provider({ children }) {
const value = useMemo(() => ({
  data: {}
}), []);

return (
  <MyContext.Provider value={value}>
    {children}
  </MyContext.Provider>
);
}

Debugging


1. DevTools

jsx
function DebugProvider({ children }) {
const value = useContext(MyContext);

useEffect(() => {
  console.log('Context value changed:', value);
}, [value]);

return children;
}

2. Error Boundaries para Contexto

jsx
class ContextErrorBoundary extends React.Component {
state = { hasError: false };

static getDerivedStateFromError(error) {
  return { hasError: true };
}

render() {
  if (this.state.hasError) {
    return <h1>Error en el contexto</h1>;
  }
  return this.props.children;
}
}