Volver a React Básico
Context API y Hooks Personalizados
Context evita el "prop drilling" al compartir estado global a través del árbol de componentes.
Creando Context
import { createContext, useContext, useState, ReactNode } from 'react';
interface TemaContextType {
tema: 'claro' | 'oscuro';
cambiarTema: () => void;
}
const TemaContext = createContext<TemaContextType | null>(null);
// Hook personalizado para consumir context de forma segura
export function useTema() {
const contexto = useContext(TemaContext);
if (!contexto) throw new Error("useTema debe usarse dentro de <TemaProveedor>");
return contexto;
}
export function TemaProveedor({ children }: { children: ReactNode }) {
const [tema, setTema] = useState<'claro' | 'oscuro'>('claro');
const cambiarTema = () => setTema(t => t === 'claro' ? 'oscuro' : 'claro');
return (
<TemaContext.Provider value={{ tema, cambiarTema }}>
{children}
</TemaContext.Provider>
);
}
Usando Context
function BotonTema() {
const { tema, cambiarTema } = useTema();
return (
<button onClick={cambiarTema}>
Tema actual: {tema}
</button>
);
}
function App() {
return (
<TemaProveedor>
<BotonTema />
{/* Cualquier componente anidado puede usar useTema() */}
</TemaProveedor>
);
}
Hooks Personalizados
Los hooks personalizados extraen lógica reutilizable en funciones:
// useLocalStorage — persiste estado en localStorage
function useLocalStorage<T>(clave: string, valorInicial: T) {
const [estado, setEstado] = useState<T>(() => {
try {
const item = localStorage.getItem(clave);
return item ? JSON.parse(item) : valorInicial;
} catch {
return valorInicial;
}
});
const setValor = (valor: T | ((prev: T) => T)) => {
const nuevoValor = valor instanceof Function ? valor(estado) : valor;
setEstado(nuevoValor);
localStorage.setItem(clave, JSON.stringify(nuevoValor));
};
return [estado, setValor] as const;
}
// Uso
function Configuracion() {
const [volumen, setVolumen] = useLocalStorage('volumen', 50);
return <input type="range" value={volumen} onChange={e => setVolumen(Number(e.target.value))} />;
}
useDebounce
function useDebounce<T>(valor: T, retraso: number): T {
const [valDebounced, setValDebounced] = useState(valor);
useEffect(() => {
const id = setTimeout(() => setValDebounced(valor), retraso);
return () => clearTimeout(id);
}, [valor, retraso]);
return valDebounced;
}
function BarraBusqueda() {
const [consulta, setConsulta] = useState('');
const consultaDebounced = useDebounce(consulta, 300);
useEffect(() => {
if (consultaDebounced) {
fetch(`/api/buscar?q=${consultaDebounced}`).then(/* ... */);
}
}, [consultaDebounced]);
return (
<input
value={consulta}
onChange={e => setConsulta(e.target.value)}
placeholder="Buscar..."
/>
);
}
useFetch Genérico
function useFetch<T>(url: string) {
const [datos, setDatos] = useState<T | null>(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let activo = true;
setCargando(true);
fetch(url)
.then(r => r.json())
.then(d => { if (activo) { setDatos(d); setCargando(false); } })
.catch(e => { if (activo) { setError(e); setCargando(false); } });
return () => { activo = false; };
}, [url]);
return { datos, cargando, error };
}
Los hooks personalizados deben comenzar con
usepara que React aplique las reglas de hooks correctamente.