9. Hooks en React
🪝 9.1 Introducción a los Hooks
Section titled “🪝 9.1 Introducción a los Hooks”¿Qué son los Hooks?
Section titled “¿Qué son los Hooks?”Los Hooks son funciones especiales de React que permiten usar estado y otras características de React en componentes funcionales.
| Hook | Propósito |
|---|---|
useState | Manejar estado local |
useEffect | Efectos secundarios |
useRef | Referencias a elementos DOM |
useContext | Acceder a contexto |
useMemo | Memorizar valores |
useCallback | Memorizar funciones |
useReducer | Estado complejo |
Historia de los Hooks
Section titled “Historia de los Hooks”// ANTES de Hooks (componentes de clase)class Contador extends React.Component {constructor(props) { super(props); this.state = { count: 0 };}
render() { return ( <button onClick={() => this.setState({ count: this.state.count + 1 })}> Count: {this.state.count} </button> );}}
// DESPUÉS de Hooks (componentes funcionales)import { useState } from 'react';
function Contador() {const [count, setCount] = useState(0);
return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button>);}Ventajas de los Hooks
Section titled “Ventajas de los Hooks”- Código más simple: Menos boilerplate que clases
- Reutilización: Crear hooks personalizados
- Composición: Combinar múltiples hooks
- Testing: Más fácil de probar
⚡ 9.2 useEffect - Efectos secundarios
Section titled “⚡ 9.2 useEffect - Efectos secundarios”¿Qué es useEffect?
Section titled “¿Qué es useEffect?”useEffect permite ejecutar código después del renderizado. Se usa para:
- Llamadas a APIs
- Suscripciones
- Manipulación del DOM
- Timers
Sintaxis básica
Section titled “Sintaxis básica”import { useEffect } from 'react';
function MiComponente() {useEffect(() => { // Este código se ejecuta después del render console.log("Componente renderizado");});
return <div>Hola</div>;}Array de dependencias
Section titled “Array de dependencias”import { useState, useEffect } from 'react';
function Ejemplo() {const [count, setCount] = useState(0);const [nombre, setNombre] = useState("");
// Se ejecuta en CADA renderuseEffect(() => { console.log("Cada render");});
// Se ejecuta solo al MONTAR (una vez)useEffect(() => { console.log("Solo al montar");}, []); // Array vacío
// Se ejecuta cuando COUNT cambiauseEffect(() => { console.log("Count cambió:", count);}, [count]); // Dependencia: count
// Se ejecuta cuando COUNT o NOMBRE cambianuseEffect(() => { console.log("Count o nombre cambió");}, [count, nombre]); // Múltiples dependencias
return <div>...</div>;}Resumen de dependencias
Section titled “Resumen de dependencias”| Dependencias | Cuándo se ejecuta |
|---|---|
| Sin array | Cada render |
[] | Solo al montar |
[valor] | Al montar + cuando valor cambia |
[a, b] | Al montar + cuando a o b cambian |
🔄 9.3 useEffect - Fetch de datos
Section titled “🔄 9.3 useEffect - Fetch de datos”Cargar datos de una API
Section titled “Cargar datos de una API”import { useState, useEffect } from 'react';
function ListaUsuarios() {const [usuarios, setUsuarios] = useState([]);const [cargando, setCargando] = useState(true);const [error, setError] = useState(null);
useEffect(() => { // Función async dentro del useEffect const fetchUsuarios = async () => { try { const response = await fetch('https://api.example.com/usuarios'); const data = await response.json(); setUsuarios(data); } catch (err) { setError(err.message); } finally { setCargando(false); } };
fetchUsuarios();}, []); // Solo al montar
if (cargando) return <p>Cargando...</p>;if (error) return <p>Error: {error}</p>;
return ( <ul> {usuarios.map(u => <li key={u.id}>{u.nombre}</li>)} </ul>);}Fetch con parámetros
Section titled “Fetch con parámetros”import { useState, useEffect } from 'react';
function DetalleUsuario({ userId }) {const [usuario, setUsuario] = useState(null);const [cargando, setCargando] = useState(true);
useEffect(() => { setCargando(true);
fetch(`https://api.example.com/usuarios/${userId}`) .then(res => res.json()) .then(data => { setUsuario(data); setCargando(false); });}, [userId]); // Se ejecuta cuando userId cambia
if (cargando) return <p>Cargando...</p>;
return ( <div> <h1>{usuario.nombre}</h1> <p>{usuario.email}</p> </div>);}🧹 9.4 useEffect - Cleanup (limpieza)
Section titled “🧹 9.4 useEffect - Cleanup (limpieza)”¿Qué es el cleanup?
Section titled “¿Qué es el cleanup?”El cleanup es una función que se ejecuta antes de que el efecto se vuelva a ejecutar o cuando el componente se desmonta. Es útil para:
- Cancelar suscripciones
- Limpiar timers
- Cancelar peticiones
useEffect(() => {// Código del efecto
return () => { // Código de limpieza (cleanup)};}, [dependencias]);Ejemplo: Timer
Section titled “Ejemplo: Timer”import { useState, useEffect } from 'react';
function Cronometro() {const [segundos, setSegundos] = useState(0);
useEffect(() => { // Crear el intervalo const intervalo = setInterval(() => { setSegundos(prev => prev + 1); }, 1000);
// Cleanup: limpiar el intervalo return () => { clearInterval(intervalo); };}, []); // Solo al montar
return <p>Segundos: {segundos}</p>;}Ejemplo: Event listener
Section titled “Ejemplo: Event listener”import { useState, useEffect } from 'react';
function TamañoVentana() {const [tamaño, setTamaño] = useState({ width: window.innerWidth, height: window.innerHeight});
useEffect(() => { const handleResize = () => { setTamaño({ width: window.innerWidth, height: window.innerHeight }); };
// Agregar listener window.addEventListener('resize', handleResize);
// Cleanup: remover listener return () => { window.removeEventListener('resize', handleResize); };}, []);
return ( <p> Ventana: {tamaño.width} x {tamaño.height} </p>);}Ejemplo: Cancelar fetch
Section titled “Ejemplo: Cancelar fetch”import { useState, useEffect } from 'react';
function BuscadorUsuarios({ query }) {const [resultados, setResultados] = useState([]);
useEffect(() => { // AbortController para cancelar fetch const controller = new AbortController();
fetch(`/api/buscar?q=${query}`, { signal: controller.signal }) .then(res => res.json()) .then(data => setResultados(data)) .catch(err => { if (err.name !== 'AbortError') { console.error(err); } });
// Cleanup: cancelar fetch si query cambia return () => { controller.abort(); };}, [query]);
return ( <ul> {resultados.map(r => <li key={r.id}>{r.nombre}</li>)} </ul>);}📌 9.5 useRef - Referencias
Section titled “📌 9.5 useRef - Referencias”¿Qué es useRef?
Section titled “¿Qué es useRef?”useRef crea una referencia mutable que persiste durante toda la vida del componente. Se usa para:
- Acceder a elementos del DOM
- Guardar valores que no causan re-render
- Mantener referencias a timers
import { useRef } from 'react';
function Ejemplo() {// Crear referenciaconst miRef = useRef(valorInicial);
// Acceder al valorconsole.log(miRef.current);
// Modificar el valor (no causa re-render)miRef.current = nuevoValor;}Acceder a elementos DOM
Section titled “Acceder a elementos DOM”import { useRef } from 'react';
function FormularioConFoco() {const inputRef = useRef(null);
const enfocarInput = () => { // Acceder al elemento DOM inputRef.current.focus();};
return ( <div> <input ref={inputRef} type="text" /> <button onClick={enfocarInput}>Enfocar</button> </div>);}Guardar valores sin re-render
Section titled “Guardar valores sin re-render”import { useState, useRef, useEffect } from 'react';
function Contador() {const [count, setCount] = useState(0);const renderCount = useRef(0);
useEffect(() => { // Incrementar sin causar re-render renderCount.current++;});
return ( <div> <p>Count: {count}</p> <p>Renders: {renderCount.current}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div>);}Guardar valor anterior
Section titled “Guardar valor anterior”import { useState, useRef, useEffect } from 'react';
function usePrevious(value) {const ref = useRef();
useEffect(() => { ref.current = value;}, [value]);
return ref.current;}
function Contador() {const [count, setCount] = useState(0);const prevCount = usePrevious(count);
return ( <div> <p>Actual: {count}</p> <p>Anterior: {prevCount}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div>);}📏 9.6 Reglas de los Hooks
Section titled “📏 9.6 Reglas de los Hooks”Las 2 reglas principales
Section titled “Las 2 reglas principales”| Regla | Descripción |
|---|---|
| Solo en el nivel superior | No usar hooks dentro de loops, condiciones o funciones anidadas |
| Solo en componentes React | No usar hooks en funciones JavaScript normales |
Ejemplos de violaciones
Section titled “Ejemplos de violaciones”// ❌ MAL: Hook dentro de condiciónfunction Componente({ mostrar }) {if (mostrar) { const [valor, setValor] = useState(0); // Error}}
// ❌ MAL: Hook dentro de loopfunction Componente({ items }) {items.forEach(item => { useEffect(() => {}); // Error});}
// ❌ MAL: Hook en función normalfunction calcular() {const [valor, setValor] = useState(0); // Errorreturn valor * 2;}Forma correcta
Section titled “Forma correcta”// ✅ BIEN: Hooks al inicio del componentefunction Componente({ mostrar }) {const [valor, setValor] = useState(0);const [otro, setOtro] = useState("");
useEffect(() => { // Efecto}, []);
// La condición va DESPUÉS de los hooksif (!mostrar) { return null;}
return <div>{valor}</div>;}
// ✅ BIEN: Hook personalizado (empieza con "use")function useContador(inicial) {const [count, setCount] = useState(inicial);const incrementar = () => setCount(c => c + 1);return { count, incrementar };}Plugin ESLint
Section titled “Plugin ESLint”// Instalar plugin de ESLint para hooksnpm install eslint-plugin-react-hooks --save-dev
// .eslintrc.js{"plugins": ["react-hooks"],"rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn"}}🔧 9.7 Hooks personalizados
Section titled “🔧 9.7 Hooks personalizados”¿Qué son los hooks personalizados?
Section titled “¿Qué son los hooks personalizados?”Los hooks personalizados son funciones que empiezan con use y pueden usar otros hooks. Permiten reutilizar lógica entre componentes.
Ejemplo: useLocalStorage
Section titled “Ejemplo: useLocalStorage”import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {// Obtener valor inicial de localStorageconst [value, setValue] = useState(() => { const saved = localStorage.getItem(key); return saved ? JSON.parse(saved) : initialValue;});
// Guardar en localStorage cuando cambieuseEffect(() => { localStorage.setItem(key, JSON.stringify(value));}, [key, value]);
return [value, setValue];}
// Usofunction App() {const [nombre, setNombre] = useLocalStorage('nombre', '');
return ( <input value={nombre} onChange={e => setNombre(e.target.value)} placeholder="Tu nombre se guardará" />);}Ejemplo: useFetch
Section titled “Ejemplo: useFetch”import { useState, useEffect } from 'react';
function useFetch(url) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);
useEffect(() => { setLoading(true);
fetch(url) .then(res => res.json()) .then(data => { setData(data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); });}, [url]);
return { data, loading, error };}
// Usofunction Usuarios() {const { data, loading, error } = useFetch('/api/usuarios');
if (loading) return <p>Cargando...</p>;if (error) return <p>Error: {error}</p>;
return ( <ul> {data.map(u => <li key={u.id}>{u.nombre}</li>)} </ul>);}Ejemplo: useToggle
Section titled “Ejemplo: useToggle”import { useState, useCallback } from 'react';
function useToggle(initialValue = false) {const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => { setValue(v => !v);}, []);
return [value, toggle];}
// Usofunction Modal() {const [isOpen, toggleOpen] = useToggle(false);
return ( <div> <button onClick={toggleOpen}> {isOpen ? 'Cerrar' : 'Abrir'} </button> {isOpen && <div className="modal">Contenido</div>} </div>);}📝 Resumen
Section titled “📝 Resumen”
🐝