Inicio > Cursos > React > Proyecto: App de Películas
ReactPatrones AvanzadosLección 11 de 21

Proyecto: App de Películas

Este proyecto integra los conceptos del módulo de Hooks Esenciales: useState para manejar el texto de búsqueda, useEffect para llamar a la API de OMDB cuando cambia la búsqueda, y un custom hook usePeliculas para encapsular toda esa lógica de fetch.

Construir proyectos completos es la forma más efectiva de aprender: los conceptos individuales son simples, pero combinarlos correctamente para que no haya bugs, fugas de memoria ni comportamiento raro requiere práctica real.

El patrón debounce implementado (setTimeout de 500ms en el useEffect) es fundamental en cualquier buscador. Sin él, al escribir "react" se harían 5 peticiones separadas al servidor (r, re, rea, reac, react), desperdiciando ancho de banda. Con debounce, se espera a que el usuario deje de escribir por 500ms antes de buscar.

El cleanup (return () => clearTimeout(timer)) cancela el timer si el usuario sigue escribiendo, asegurando que solo la búsqueda completa final llegue a la API.

// usePeliculas.js - Custom hook
function usePeliculas(busqueda) {
  const [peliculas, setPeliculas] = useState([]);
  const [cargando, setCargando] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    if (!busqueda || busqueda.length < 3) return;
    
    const buscar = async () => {
      setCargando(true);
      setError(null);
      try {
        const resp = await fetch(
          `https://www.omdbapi.com/?s=${busqueda}&apikey=TU_KEY`
        );
        const data = await resp.json();
        if (data.Response === "False") throw new Error(data.Error);
        setPeliculas(data.Search);
      } catch (err) {
        setError(err.message);
        setPeliculas([]);
      } finally {
        setCargando(false);
      }
    };
    
    const timer = setTimeout(buscar, 500); // Debounce
    return () => clearTimeout(timer);
  }, [busqueda]);
  
  return { peliculas, cargando, error };
}

// App.jsx
function App() {
  const [busqueda, setBusqueda] = useState("");
  const { peliculas, cargando, error } = usePeliculas(busqueda);
  
  return (
    <div className="app">
      <h1>🎬 Buscador de Películas</h1>
      <input
        value={busqueda}
        onChange={e => setBusqueda(e.target.value)}
        placeholder="Busca una película..."
      />
      {cargando && <p>Buscando...</p>}
      {error && <p className="error">{error}</p>}
      <div className="grid">
        {peliculas.map(p => (
          <div key={p.imdbID} className="pelicula">
            <img src={p.Poster} alt={p.Title} />
            <h3>{p.Title}</h3>
            <p>{p.Year}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
Consejo⏱️ El debounce (setTimeout 500ms) evita hacer una petición por cada tecla. Espera a que el usuario deje de escribir para buscar.
← useReducer y Estado ComplejoReact Router: Rutas Básicas →

Practica esta lección

Resuelve los ejercicios del curso de React en la app, gratis y con corrección automática.

Empezar a practicar