Back

Guía completa de desplazamiento infinito en React

Guía completa de desplazamiento infinito en React

El desplazamiento infinito es un patrón de UX donde el nuevo contenido se carga a medida que los usuarios se desplazan hacia abajo en una página. Común en feeds sociales, líneas de tiempo y aplicaciones de noticias, es una forma fluida de explorar grandes conjuntos de datos sin usar botones de paginación. En esta guía, aprenderás cómo implementar el desplazamiento infinito en React utilizando tanto una biblioteca de terceros como un hook personalizado.

Puntos clave

  • Aprende 2 formas prácticas de implementar el desplazamiento infinito en React
  • Usa un hook personalizado con IntersectionObserver o un paquete de terceros
  • Maneja estados de carga, paginación y resultados vacíos

¿Qué es el desplazamiento infinito?

El desplazamiento infinito carga nuevos datos a medida que el usuario se desplaza cerca del final de una lista. Elimina la necesidad de paginación y crea una experiencia de navegación continua. Lo verás en productos como Instagram, Twitter y Reddit.

Método 1: Usando un paquete (react-infinite-scroll-component)

La forma más sencilla de implementar el desplazamiento infinito en React es con un paquete bien probado.

Paso 1: Instalación

npm install react-infinite-scroll-component

Paso 2: Ejemplo de uso básico

import InfiniteScroll from 'react-infinite-scroll-component';

function Feed() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  const fetchMoreData = async () => {
    const res = await fetch(`/api/posts?page=${page}`);
    const newItems = await res.json();
    if (newItems.length === 0) setHasMore(false);
    else {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }
  };

  return (
    <InfiniteScroll
      dataLength={items.length}
      next={fetchMoreData}
      hasMore={hasMore}
      loader={<h4>Loading...</h4>}
      endMessage={<p>No more results</p>}
    >
      {items.map(item => <div key={item.id}>{item.title}</div>)}
    </InfiniteScroll>
  );
}

Ventajas

  • Rápido de configurar
  • Estados de carga y finalización incorporados

Desventajas

  • Menos control sobre la lógica de desplazamiento
  • Añade dependencia de paquetes

Método 2: Hook personalizado con IntersectionObserver

Este método te da control total y sin dependencias adicionales.

Paso 1: Crear un hook

function useInfiniteScroll(callback, ref) {
  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) callback();
    });
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [callback, ref]);
}

Paso 2: Usar en un componente

function Feed() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);
  const sentinelRef = useRef(null);

  const loadMore = async () => {
    if (!hasMore) return;
    const res = await fetch(`/api/posts?page=${page}`);
    const newItems = await res.json();
    if (newItems.length === 0) setHasMore(false);
    else {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }
  };

  useInfiniteScroll(loadMore, sentinelRef);

  return (
    <div>
      {items.map(item => <div key={item.id}>{item.title}</div>)}
      <div ref={sentinelRef} style={{ height: 1 }} />
    </div>
  );
}

Ventajas

  • Completamente personalizable
  • Funciona con cualquier lógica de desplazamiento o API

Desventajas

  • Requiere más configuración
  • IntersectionObserver no es compatible con IE11

Patrones y consejos del mundo real

  • Aplicar debounce a las llamadas fetch para evitar sobrecargar las APIs
  • Mostrar un spinner o cargador esqueleto para mejorar la UX
  • Añadir lógica de reintento para errores de red
  • Manejar casos extremos como resultados cero o páginas vacías
if (items.length === 0 && !hasMore) {
  return <p>No posts found.</p>;
}

Consideraciones de rendimiento

  • Usa bibliotecas como react-window para virtualización cuando renderices miles de elementos
  • Memoriza componentes de elementos para prevenir re-renderizados
  • Limpia los observadores adecuadamente para evitar fugas de memoria
useEffect(() => {
  const observer = new IntersectionObserver(...);
  return () => observer.disconnect();
}, []);

Conclusión

El desplazamiento infinito es una característica común en las interfaces de usuario modernas. Ya sea que prefieras la simplicidad de un paquete o el control de un hook personalizado, React hace que ambos enfoques sean accesibles. Siempre considera el rendimiento y la retroalimentación de carga al implementar el desplazamiento infinito.

Preguntas frecuentes

Usar una biblioteca como `react-infinite-scroll-component` te proporciona una configuración rápida y conveniente.

Establece una bandera `hasMore` en false cuando la API devuelve un array vacío.

Puede hacerlo, si estás renderizando demasiados elementos. Utiliza herramientas de virtualización como `react-window` para gestionar listas grandes.

Usa `IntersectionObserver` para activar la carga de datos cuando un `div` inferior (centinela) entre en el campo de visión.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers