Saltar al contenido

Lección 13 de 13 · Lectura y práctica

Patrones comunes de useEffect

Patrones practicos Fetch con async/await function Users() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // No puedes hacer.

Del concepto a la práctica

Taller de esta lección

Una búsqueda local con retraso combina estado de entrada, efecto y limpieza del temporizador. Si el usuario escribe varias letras rápidamente, solo conviene procesar el valor que permanezca estable durante el intervalo. El resultado se calcula desde la colección y la consulta estable; solo el temporizador exige limpieza.

Ejemplo resuelto

El bloque autónomo revisado se encuentra más abajo. Con tres libros y consulta mar, si dos títulos contienen “mar”, la salida es 2 resultados.

Pruébalo

  1. Define tres títulos conocidos.

  2. Cambia la consulta con un campo.

  3. Espera 300 ms tras escribir.

  4. Comprueba el número filtrado.

Comprueba tu resultado

La consulta vacía debe devolver los tres libros.

Error frecuente: omitir la limpieza y acumular temporizadores; corrígelo con clearTimeout(timer). No guardes un nombre completo derivado si puedes construirlo durante el render.

Debounce sin resultado duplicado

El componente completo siguiente difiere el valor de consulta y calcula la colección filtrada durante render; no guarda una segunda copia de resultados. El temporizador es el recurso externo que se limpia. El input del fragmento anterior estaba ausente y ahora permite probar la interacción.

jsx
import {useEffect,useState} from 'react';
const LIBROS=[{id:'b1',titulo:'Mar abierto'},{id:'b2',titulo:'Mareas'},{id:'b3',titulo:'Ríos'}];
export default function Buscar(){
  const [consulta,setConsulta]=useState('');
  const [estable,setEstable]=useState('');
  useEffect(()=>{
    const timer=setTimeout(()=>setEstable(consulta),300);
    return ()=>clearTimeout(timer);
  },[consulta]);
  const q=estable.trim().toLowerCase();
  const resultados=LIBROS.filter(l=>l.titulo.toLowerCase().includes(q));
  return <section>
    <label>Buscar<input value={consulta} onChange={e=>setConsulta(e.target.value)}/></label>
    <p>Filtro aplicado: {estable || 'Todos'}</p>
    <p>{resultados.length} resultados</p>
  </section>;
}

Vacía da3, mar da2, ríos da1 y zzz da0 después del intervalo. Durante espera se muestra el filtro anterior con su etiqueta;300ms es el mínimo solicitado al temporizador, no un SLA exacto. Una API real añade validación de HTTP/esquema y cancelación o descarte de respuestas obsoletas; limpiar solo el timeout no cancela una petición ya iniciada. localStorage puede fallar o contener JSON inválido: valida y captura errores antes de hidratar, y distingue almacenamiento de sesión de una confirmación de servidor.

En dos líneas de tiempo A empieza primero pero B termina antes; se muestra B y la respuesta tardía de A se ignora, manteniendo la petición actual B.
CursaloMapa de práctica
Acepta solo la respuesta vigente

Al cambiar la consulta, B pasa a ser la petición vigente. Si B termina antes y A llega después, se muestra B y se descarta A; una respuesta antigua tampoco debe cambiar el estado de carga actual.

Ver la infografía en tamaño completo ↗

Patrones practicos

Fetch con async/await

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // No puedes hacer useEffect async directamente
    // Crea una funcion async dentro
    const fetchUsers = async () => {
      try {
        setLoading(true);
        const res = await fetch('/api/users');

        if (!res.ok) throw new Error('Error al cargar');

        const data = await res.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Cargando...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

Debounce de busqueda

function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }

    // Espera 300ms antes de buscar
    const timer = setTimeout(() => {
      fetch(`/api/search?q=${query}`)
        .then(res => res.json())
        .then(setResults);
    }, 300);

    return () => clearTimeout(timer);
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Buscar..."
      />
      <ul>
        {results.map(r => <li key={r.id}>{r.title}</li>)}
      </ul>
    </div>
  );
}

Local Storage

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Uso
function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
      Tema: {theme}
    </button>
  );
}

Evitar efectos innecesarios

// MAL - efecto innecesario
function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
}

// BIEN - calcula durante el render
function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  // Derivado, no necesita estado
  const fullName = firstName + ' ' + lastName;
}

Sigue con React Fundamentals

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.