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 use para que React aplique las reglas de hooks correctamente.