🪝 3.2 · Hooks: useState, useEffect, useRef y compañía

⏱ 6h 00min ⚡ 100 XP 🏅 Mid Junior 📖 Frontend Moderno y Proyecto Final
"Los Hooks son la 'API moderna' de React. Sin ellos, todo sería clases y this por todos lados."

🎯 Objetivo del tema

Al terminar este tema serás capaz de: Al terminar este tema serás capaz de dominar los Hooks esenciales de React: useState para estado, useEffect para efectos secundarios, useRef para referencias al DOM, useMemo y useCallback para optimización. También aprenderás las reglas de los Hooks y custom hooks. Con esto podrás construir componentes complejos, performantes, y reutilizables.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

🗺️ Mapa del tema

Antes de los Hooks (2018), los componentes React se escribían como CLASES con this.state, this.setState, this.componentDidMount. Era verboso, difícil de reutilizar lógica, y this en JavaScript es una fuente infinita de bugs. Los Hooks llegaron para resolver TODO eso: permiten usar estado y otras features de React sin clases, con funciones simples. Hoy el 99% del código React moderno usa Hooks. Si los dominas, dominas React.

1. Las 2 reglas de oro de los Hooks

Por qué los Hooks son especiales y dónde NO debes usarlos.

2. useEffect: el hook más poderoso

Cómo ejecutar código después del render: fetches, suscripciones, timers.

3. useRef: referencias al DOM sin re-renderizar

Acceder a inputs, hacer focus, integrar librerías de terceros.

4. useMemo y useCallback: optimización

Cuándo (no) usar memoización para evitar renders innecesarios.

5. Custom Hooks: reutilizar lógica entre componentes

Tu primera abstracción real en React: extraer lógica a una función.

6. useEffect con APIs reales: REST Countries + búsqueda

Proyecto completo: filtrado en vivo, paginación, y carga de imágenes.

3.2.1 Las 2 reglas de oro de los Hooks

Los Hooks son funciones especiales que te permiten usar estado y otras features de React sin escribir clases. Tienen 2 reglas ABSOLUTAS que NUNCA debes violar: solo se llaman en el top level de un componente o custom hook, y solo en componentes funcionales o custom hooks. Si las rompes, React se comporta de formas extrañas. Es la base de todo lo demás.

📷 Imagen referencial: Ciclo de vida de un componente React con Hooks: montaje (useState inicial, useEffect se ejecuta), actualización (useEffect se re-ejecuta si cambian dependencias), desmontaje (cleanup del useEffect).
  • Regla #1: SOLO en el top level. NUNCA dentro de if, for, while, funciones anidadas, ni try/catch.
  • Regla #2: SOLO en componentes funcionales de React o en otros Hooks personalizados (custom hooks). NUNCA en funciones normales de JavaScript.
// ✅ CORRECTO: useState en el top level
function Contador() {
  const [count, setCount] = useState(0);
  const [loading, setLoading] = useState(false);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ❌ INCORRECTO: useState dentro de un if (rompe la regla #1)
function Contador() {
  if (Math.random() > 0.5) {
    const [count, setCount] = useState(0);  // ERROR: hook en condicional
  }
  return <p>Hola</p>;
}

// ❌ INCORRECTO: useState en una función normal (rompe la regla #2)
function contar() {
  const [count, setCount] = useState(0);  // ERROR: no es componente ni hook
  return count;
}

// ✅ CORRECTO: Hook en un custom hook
function useContadorInicial(valorInicial) {
  const [count, setCount] = useState(valorInicial);
  return { count, incrementar: () => setCount(c => c + 1) };
}

function MiComponente() {
  const { count, incrementar } = useContadorInicial(0);  // OK, custom hook
  return <button onClick={incrementar}>{count}</button>;
}

Las 2 reglas de los Hooks: correcto vs incorrecto

⭐ ESLint plugin react-hooks: tu salvavidas: El linter de React (eslint-plugin-react-hooks) te avisa AUTOMÁTICAMENTE si violas alguna regla. Es uno de los plugins más importantes: si tu VS Code no lo tiene configurado, instálalo YA. Te ahorra horas de debugging. La regla 'exhaustive-deps' te obliga a poner TODAS las dependencias que usas dentro del useEffect en el array, evitando bugs sutiles.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.2.2 useEffect: el hook más poderoso

useEffect es para EFECTOS SECUNDARIOS: todo lo que pasa 'después' del render. Fetch a una API, suscribirte a eventos, iniciar un timer, modificar el DOM manualmente. Es la traducción moderna de los lifecycle methods de clase (componentDidMount, componentDidUpdate, componentWillUnmount), pero con una API más simple. Lo usarás en el 80% de tus componentes que interactúan con el mundo exterior.

Las 3 formas de useEffect según dependencias

// 1. SIN array de dependencias: se ejecuta en CADA render (casi siempre MALO)
useEffect(() => {
  console.log('Se ejecuta en cada render');
});  // ⚠️ posible loop infinito si hace fetch

// 2. Con array VACÍO []: se ejecuta SOLO al montar (fetch inicial)
useEffect(() => {
  fetch('/api/datos').then(r => r.json()).then(setData);
}, []);  // ✅ patrón para cargar datos iniciales

// 3. Con dependencias [dep1, dep2]: se ejecuta cuando cambian esas deps
useEffect(() => {
  document.title = `Hola, ${nombre}`;  // actualiza el título cuando cambia nombre
}, [nombre]);  // ✅ se ejecuta solo cuando nombre cambia

// 4. Con función de CLEANUP (return): para limpiar el efecto al desmontar
useEffect(() => {
  const timer = setInterval(() => console.log('tick'), 1000);
  return () => clearInterval(timer);  // cleanup: se ejecuta al desmontar
}, []);

Las 4 formas de useEffect

Ejemplo completo: contador con título dinámico

import { useState, useEffect } from 'react';

function ContadorConTitulo() {
  const [count, setCount] = useState(0);

  // Efecto 1: actualizar el título de la página cuando cambia count
  useEffect(() => {
    document.title = `Contador: ${count} clics`;
  }, [count]);  // se ejecuta cuando count cambia

  // Efecto 2: log al montar (solo una vez)
  useEffect(() => {
    console.log('Componente montado');
    return () => console.log('Componente desmontado');  // cleanup
  }, []);  // [] = solo al montar/desmontar

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

useEffect con cleanup y dependencias

⭐ El array de dependencias NO es opcional: El array de dependencias de useEffect NO es opcional. Es la regla de oro: si tu efecto usa una variable externa, ponla en el array. Si no, React no sabe que debe re-ejecutarse. Ejemplo: si tu efecto hace fetch(userId) pero el array está vacío, NUNCA se vuelve a fetch aunque userId cambie. La regla 'exhaustive-deps' de ESLint te obliga a poner TODAS las deps. Es uno de los errores más sutiles y más comunes de React.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.2.3 useRef: referencias al DOM sin re-renderizar

useRef te da una CAJA que persiste entre renders SIN causar re-render. Es perfecta para: acceder al DOM directamente (focus en un input, scroll a una sección, reproducir video), guardar valores que no deben causar render (timers, contadores), integrar librerías de terceros. Es el hook de los detalles de bajo nivel: cuando useState es demasiado, useRef es lo que necesitas.

import { useRef, useEffect } from 'react';

// 1. Focus automático en un input al cargar
function BuscadorAutoFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();  // hace focus en el input al montar
  }, []);

  return <input ref={inputRef} placeholder="Buscar..." />;
}

// 2. Guardar el valor anterior de un estado (sin causar render)
function ContadorConAnterior() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef(0);

  useEffect(() => {
    prevCountRef.current = count;  // se actualiza sin re-render
  });

  return (
    <div>
      <p>Actual: {count}</p>
      <p>Anterior: {prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// 3. Acceso a un elemento de video para controlarlo
function ReproductorVideo() {
  const videoRef = useRef(null);

  const play = () => videoRef.current.play();
  const pause = () => videoRef.current.pause();

  return (
    <div>
      <video ref={videoRef} src="video.mp4" />
      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
    </div>
  );
}

// Regla de useRef: la mutación de .current NO causa re-render.
// Es diferente de useState. Si necesitas que la UI se actualice, usa useState.

useRef: 3 usos comunes (focus, valor anterior, DOM)

⭐ useRef NO causa re-render: useRef.current se puede MUTAR sin causar re-render. Esto es ÚTIL para detalles de bajo nivel (focus, scroll, integrar librerías) pero PELIGROSO si esperas que la UI se actualice. La regla: si tu UI debe CAMBIAR cuando el valor cambia, usa useState. Si solo necesitas el valor sin causar render, usa useRef. Es la diferencia entre un contador visible (useState) y un ID de un timer (useRef).
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.2.4 useMemo y useCallback: optimización

useMemo y useCallback son hooks de OPTIMIZACIÓN: memorizan (recuerdan) un valor o función para no recalcularlo/recrearlo en cada render. Son útiles SOLO cuando tienes un cálculo costoso o pasas funciones a componentes memoizados. Si los usas sin necesidad, solo agregas complejidad. La regla: primero mide, después optimiza.

useMemo: memoriza un valor calculado

import { useMemo, useState } from 'react';

// SIN useMemo: el filtro se ejecuta en CADA render (lento si hay 10000 items)
function ListaConFiltro({ items, termino }) {
  const [extra, setExtra] = useState(0);
  // Este filter se ejecuta en CADA render, incluso cuando cambia 'extra'
  const filtrados = items.filter(item => item.name.includes(termino));
  return <>{filtrados.length} items</>;
}

// CON useMemo: el filtro SOLO se ejecuta cuando items o termino cambian
function ListaConFiltroOptimizado({ items, termino }) {
  const [extra, setExtra] = useState(0);
  // Solo se recalcula si items o termino cambian (no cuando cambia 'extra')
  const filtrados = useMemo(
    () => items.filter(item => item.name.includes(termino)),
    [items, termino]  // dependencias: solo recalcular si estas cambian
  );
  return <>{filtrados.length} items</>;
}

useMemo: cuándo y por qué

useCallback: memoriza una función

import { useCallback, useState, memo } from 'react';

// Componente hijo memoizado: solo se re-renderiza si sus props cambian
const Boton = memo(function Boton({ onClick, children }) {
  console.log('Boton renderizado');
  return <button onClick={onClick}>{children}</button>;
});

// SIN useCallback: handleClick es una función NUEVA en cada render → Boton se re-renderiza
function PadreSinCallback() {
  const [count, setCount] = useState(0);
  const handleClick = () => console.log('click');  // función nueva cada render
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Contador: {count}</button>
      <Boton onClick={handleClick}>Click me</Boton>
    </div>
  );
}

// CON useCallback: handleClick es la MISMA función si count no cambia → Boton NO se re-renderiza
function PadreConCallback() {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => console.log('click'), []);  // función memorizada
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Contador: {count}</button>
      <Boton onClick={handleClick}>Click me</Boton>
    </div>
  );
}

useCallback: para componentes memoizados

⭐ Solo optimiza si hay un problema real: El 80% de las apps React NO necesitan useMemo ni useCallback. Son micro-optimizaciones que solo importan cuando tienes: (1) listas de 10,000+ items con cálculos costosos, (2) componentes memoizados (React.memo) que reciben callbacks, (3) renders muy frecuentes. Si tu app tiene 50 items y no notas lag, no optimices prematuramente. La regla de optimización: PRIMERO mide con React DevTools Profiler, DESPUÉS optimiza. Sin medición, es adivinar.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.2.5 Custom Hooks: reutilizar lógica

Los Custom Hooks son la forma de extraer lógica reutilizable entre componentes. Es la primera abstracción REAL de React: en vez de copiar useState + useEffect en cada componente que hace fetch, creas un useFetch que devuelve { data, loading, error }. Es como una función que encapsula lógica de React. Si lo entiendes, empiezas a pensar como un senior.

Custom hook #1: useFetch (reutilizable en TODA tu app)

import { useState, useEffect } from 'react';

// Custom hook: encapsula la lógica de fetch
function useFetch(url) {
  const [data, setData] = useState(null);
  const [cargando, setCargando] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelado = false;  // para evitar setState si el componente se desmonta
    setCargando(true);
    fetch(url)
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(data => { if (!cancelado) setData(data); })
      .catch(err => { if (!cancelado) setError(err.message); })
      .finally(() => { if (!cancelado) setCargando(false); });

    return () => { cancelado = true; };  // cleanup
  }, [url]);  // re-fetch si cambia la URL

  return { data, cargando, error };
}

// Uso en cualquier componente (¡3 líneas!):
function ListaDePersonajes() {
  const { data, cargando, error } = useFetch('https://rickandmortyapi.com/api/character');
  if (cargando) return <p>Cargando...</p>;
  if (error) return <p>Error: {error}</p>;
  return <div>{data.results.map(char => <p key={char.id}>{char.name}</p>)}</div>;
}

function DetalleDePersonaje({ id }) {
  const { data, cargando, error } = useFetch(`https://rickandmortyapi.com/api/character/${id}`);
  if (cargando) return <p>Cargando...</p>;
  if (error) return <p>Error: {error}</p>;
  return <h1>{data.name}</h1>;
}

// La misma lógica, 2 componentes diferentes. SIN useFetch, tendrías que duplicar ~20 líneas de useState+useEffect en cada uno.

Custom hook useFetch: reutiliza lógica de fetch

Custom hook #2: useLocalStorage (sincroniza con localStorage)

import { useState, useEffect } from 'react';

function useLocalStorage(key, valorInicial) {
  // Leer de localStorage al iniciar (lazy initializer)
  const [valor, setValor] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : valorInicial;
    } catch {
      return valorInicial;
    }
  });

  // Sincronizar con localStorage cada vez que cambia
  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(valor));
    } catch (err) {
      console.error('Error guardando en localStorage:', err);
    }
  }, [key, valor]);

  return [valor, setValor];
}

// Uso: igual que useState, pero persiste automáticamente
function PreferenciasUsuario() {
  const [tema, setTema] = useLocalStorage('tema', 'claro');
  const [nombre, setNombre] = useLocalStorage('nombre', '');
  return (
    <div>
      <select value={tema} onChange={e => setTema(e.target.value)}>
        <option value="claro">Claro</option>
        <option value="oscuro">Oscuro</option>
      </select>
      <input value={nombre} onChange={e => setNombre(e.target.value)} />
      <p>Tu tema: {tema}, tu nombre: {nombre}</p>
    </div>
  );
}

// Cierra el navegador, ábrelo de nuevo: el tema y nombre siguen ahí.
// ¡Persistencia automática sin escribir lógica extra!

Custom hook useLocalStorage: persistencia automática

⭐ Custom hook = nombre 'use' + Hooks dentro: Un custom hook es una función que empieza con 'use' y puede llamar a otros Hooks. Por convención, SEMPRE empieza con 'use' (useFetch, useAuth, useForm). Esto le dice a React (y al linter) que puede usar Hooks dentro, y que debe respetar las reglas de Hooks. La regla para crear un custom hook: si la misma lógica useState+useEffect aparece en 2+ componentes, extráela a un custom hook. Es la forma PROFESIONAL de organizar lógica en React.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.2.6 useEffect con APIs reales: REST Countries + búsqueda

Proyecto integrador: vamos a construir un buscador de países en vivo usando la API pública REST Countries (https://restcountries.com/), que es 100% gratis, sin auth, y devuelve datos de TODOS los países del mundo (nombre, capital, población, bandera, idiomas, etc.). Vas a combinar: useState para los datos y la búsqueda, useEffect para cargar inicialmente, useMemo para filtrar eficientemente, y debounce para no filtrar en cada tecla (optimización).

API pública: REST Countries (gratis, sin auth)

  • Endpoint: https://restcountries.com/v3.1/all?fields=name,capital,population,flag,languages,region
  • Sin auth ni API key: solo haz fetch.
  • Devuelve array de 250+ países con datos completos.
  • Documentación: https://restcountries.com/
  • Búsqueda por nombre: https://restcountries.com/v3.1/name/peru
  • Filtro por región: https://restcountries.com/v3.1/region/americas

El proyecto completo: buscador de países

import { useState, useEffect, useMemo } from 'react';

function PaisesApp() {
  const [paises, setPaises] = useState([]);
  const [busqueda, setBusqueda] = useState('');
  const [region, setRegion] = useState('all');
  const [cargando, setCargando] = useState(true);
  const [error, setError] = useState(null);

  // Cargar países al montar
  useEffect(() => {
    fetch('https://restcountries.com/v3.1/all?fields=name,capital,population,flag,languages,region')
      .then(r => r.json())
      .then(data => setPaises(data))
      .catch(err => setError(err.message))
      .finally(() => setCargando(false));
  }, []);

  // Filtrar con useMemo (solo se recalcula si cambia busqueda, region, o paises)
  const paisesFiltrados = useMemo(() => {
    return paises.filter(p => {
      const matchNombre = p.name.common.toLowerCase().includes(busqueda.toLowerCase());
      const matchRegion = region === 'all' || p.region === region;
      return matchNombre && matchRegion;
    });
  }, [paises, busqueda, region]);

  if (cargando) return <p>Cargando 250+ países...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div className="paises-app">
      <h1>🌍 Buscador de Países ({paisesFiltrados.length} resultados)</h1>

      <input
        type="text"
        placeholder="Buscar país..."
        value={busqueda}
        onChange={e => setBusqueda(e.target.value)}
      />

      <select value={region} onChange={e => setRegion(e.target.value)}>
        <option value="all">Todas las regiones</option>
        <option value="Americas">Américas</option>
        <option value="Europe">Europa</option>
        <option value="Asia">Asia</option>
        <option value="Africa">África</option>
        <option value="Oceania">Oceanía</option>
      </select>

      <div className="paises-grid">
        {paisesFiltrados.map(p => (
          <article key={p.cca3} className="pais-card">
            <img src={p.flags.png} alt={p.name.common} />
            <h2>{p.name.common}</h2>
            <p><strong>Capital:</strong> {p.capital?.[0]}</p>
            <p><strong>Población:</strong> {p.population.toLocaleString()}</p>
            <p><strong>Región:</strong> {p.region}</p>
          </article>
        ))}
      </div>
    </div>
  );
}

Buscador de países con REST Countries, useState, useEffect, useMemo

⭐ REST Countries: API perfecta para aprender: REST Countries es la mejor API para aprender a consumir datos reales: 250+ países, sin auth, sin rate limits, y devuelve datos estructurados. La usé como ejemplo porque te permite practicar TODO: useState para el array, useState para el término de búsqueda, useEffect para cargar al montar, useMemo para filtrar eficientemente, fetch con manejo de errores. Una vez que dominas este patrón, lo aplicas a CUALQUIER API: clima (OpenWeatherMap), películas (OMDB), Pokémon (PokeAPI), GitHub, etc. Es el patrón fundamental del 80% de las SPAs modernas.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

📚 Contenido ampliado

Material adicional, referencias externas verificadas y ejemplos extendidos.

🤖 AI Mission

Los Hooks se dominan construyendo, no leyendo.

Misión: Crea una app de 'Favoritos del clima' con React: una lista de ciudades favoritas que muestra el clima actual de cada una usando la API pública Open-Meteo (gratis, sin API key). Usa useState, useEffect, useMemo, y al menos 1 custom hook. Pídele a la IA que revise tu código. Anota en tu cuaderno: ¿cuál hook te costó más entender?

Pasos sugeridos

  1. Crea el proyecto: npm create vite@latest clima-favoritos -- --template react.
  2. Crea hooks/useClima.js (custom hook): recibe una ciudad, hace fetch a Open-Meteo, devuelve { data, cargando, error }.
  3. API Open-Meteo: https://api.open-meteo.com/v1/forecast?latitude=-34.6&longitude=-58.4&current_weather=true (no necesita API key, devuelve el clima actual).
  4. Crea components/ClimaCard.jsx: recibe { ciudad, data, cargando, error } y muestra: nombre, temperatura actual, velocidad del viento, hora.
  5. Crea components/ListaCiudades.jsx: array hardcoded de 5 ciudades (Buenos Aires, Madrid, CDMX, Tokio, Londres) con sus coordenadas.
  6. App.jsx: lista las 5 ciudades usando el custom hook useClima para cada una.
  7. Maneja los 3 estados: cargando (spinner), error (mensaje), éxito (datos del clima).
  8. Estiliza: grid responsive, tarjetas con color según temperatura (>25°C naranja, <10°C azul).
  9. Agrega useMemo para que el cálculo de 'color según temperatura' solo se ejecute cuando cambian los datos.
  10. Pídele a la IA: 'Tengo esta app del clima. Sugiere 2 mejoras: features (búsqueda, gráfico, pronóstico 7 días) o performance (useCallback). NO me des código, dime QUÉ agregar.'
  11. Aplica 2 mejoras. Vuelve a probar.
  12. Anota: 3 hooks que entendiste mejor al HACER, y 1 que te sigue costando.

📓 Entregable: Capturas de: la app mostrando el clima de 5 ciudades, tarjetas con colores según temperatura, código de useClima.js, y media página de cuaderno con tu reflexión.

🚫 Errores típicos de razonamiento

Error 1: Usar useEffect sin array de dependencias y causar loops infinitos.
Por qué: Si escribes useEffect(() => { fetch(...).then(setData) }, ) SIN el array, el efecto se ejecuta en CADA render. Si setData causa un nuevo render (que sí, por eso lo llamaste), tienes un loop infinito. SIEMPRE pon el array: [] para solo al montar, [dep] cuando cambien las deps. Es uno de los errores más sutiles de React.
Error 2: Olvidar el array de dependencias completo [] en useEffect.
Por qué: React te avisa con 'React Hook useEffect has a missing dependency'. Si lo ignoras con eslint-disable, el efecto NUNCA se vuelve a ejecutar aunque cambien las variables. La regla: TODA variable externa que uses dentro del efecto va en el array. Es una de las reglas de exhaustividad que ESLint enforce automáticamente.
Error 3: Usar useState cuando necesitas useRef, o viceversa.
Por qué: useState causa re-render cuando cambia. useRef NO causa re-render. Si necesitas que la UI se actualice cuando el valor cambia, usa useState. Si solo necesitas el valor sin causar render (focus, scroll, ID de timer), usa useRef. Confundirlos es el error #1 de los Hooks.
Error 4: Optimizar prematuramente con useMemo y useCallback sin necesidad.
Por qué: useMemo y useCallback agregan complejidad y consumen memoria. Si tu componente tiene 50 items y no notas lag, no los uses. La regla: primero mide con React DevTools Profiler, después optimiza. Optimización prematura es la raíz de todo mal en software.
Error 5: Llamar a un Hook dentro de un if, for, o función.
Por qué: Los Hooks deben estar en el top level del componente. React los identifica por ORDEN de llamada: si metes un useState dentro de un if que a veces se ejecuta y a veces no, React pierde la cuenta y los Hooks se desordenan. El linter react-hooks te avisa, pero la causa es: SIEMPRE Hooks en el top level, NUNCA dentro de condicionales.

🧪 Laboratorio práctico

Los Hooks se aprenden construyendo, no leyendo.

Laboratorio: App de países del mundo con REST Countries + Hooks

Objetivo: Construir un buscador de países con React + Vite + REST Countries API + Hooks (useState, useEffect, useMemo). La app debe: cargar 250+ países, tener búsqueda en vivo por nombre, filtro por región, mostrar bandera + capital + población. Practicarás todos los Hooks fundamentales.

Pasos

  1. Crea el proyecto: npm create vite@latest paises-app -- --template react.
  2. Crea components/PaisCard.jsx: recibe { pais }, muestra bandera, nombre, capital, población, región, idiomas.
  3. Crea components/Buscador.jsx: input controlado con useState para el término de búsqueda.
  4. Crea components/FiltroRegion.jsx: select con useState para la región (Todas, Américas, Europa, Asia, África, Oceanía).
  5. Crea components/ListaPaises.jsx: useState para paises, useEffect para fetch inicial, useMemo para filtrar por búsqueda y región.
  6. App.jsx: orquesta los componentes en un layout limpio.
  7. Estiliza con CSS Grid responsive: 1 col en móvil, 2 en tablet, 3-4 en desktop.
  8. Maneja los 3 estados: cargando (skeleton), error (mensaje), éxito (grid).
  9. Prueba: busca 'perú' (1 resultado), 'united' (varios), 'xyz' (sin resultados). Filtra por 'Americas'.
  10. Verifica useMemo: en DevTools > React Profiler, confirma que el filtro solo se ejecuta cuando cambia busqueda o region, no en cada render.
  11. Sube a Vercel/Netlify. Comparte URL.
  12. Haz commit: 'feat: app de países con REST Countries, useState, useEffect, useMemo'.

📓 Entregable: URL pública de la app, capturas de: 250+ países cargados, búsqueda 'peru' filtrando, filtro por región, código de los 4 componentes + custom hook si lo usaste, y commit en Git.

📓 Tu cuaderno: Anota pseudocódigo, diagramas, errores que encontraste y respuestas a "explica sin código". La escritura manual refuerza tu razonamiento. Tu profesor puede pedirte que subas fotos de páginas específicas.