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;
  }
}