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).
// 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); // 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>
);
} // 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>
);
} // 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;
} // 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>
);
} // 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>
);
} function App() {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<CartProvider>
<MainApp />
</CartProvider>
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
} 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>
);
} // useTheme.js
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme debe usarse dentro de un ThemeProvider');
}
return context;
} // Mejor: Contextos separados para diferentes responsabilidades
const UserContext = createContext();
const ThemeContext = createContext();
const SettingsContext = createContext();
// Peor: Un solo contexto para todo
const AppContext = createContext(); 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>
);
} // 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>
);
} // 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>
);
} function DebugProvider({ children }) {
const value = useContext(MyContext);
useEffect(() => {
console.log('Context value changed:', value);
}, [value]);
return children;
} 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;
}
}