Feed infinito

Un feed que nunca termina tiene que pedir datos mientras el usuario mira: lo bastante pronto para que la siguiente publicación ya esté ahí, con algo que mostrar mientras una página llega o si ha fallado, y sin dejar ninguna petición en marcha cuando el feed ya no está.

Pruébalo

Todas las versiones importan ./api, que sustituye a tu backend: fetchPosts(page, signal) devuelve una página de publicaciones y se detiene cuando la señal se aborta. Cámbialo por una llamada a tu API y quita failNextRequest, que solo controla el interruptor de fallo de la demo.

typescript
import { createSignal } from '@reelkit/core';
import { cdnUrl } from '@reelkit/example-data';

// Stands in for your backend. The recipe only relies on the signature of
// `fetchPosts`: a page number in, a page of posts out, cancellable through an
// AbortSignal.

export interface Post {
  id: string;
  title: string;
  image: string;
}

const kPageSize = 6;
const kDelayMs = 800;
const kImages = Array.from(
  { length: 9 },
  (_, i) => `samples/images/image-${String(i + 1).padStart(2, '0')}.jpg`,
);

/**
 * Demo switch: while on, the next request fails, so the error slide can be
 * seen. A signal, so the switch shows when a request has used it up. Only the
 * demo on this page uses it: delete it, and the `@reelkit/core` import with
 * it, when you replace `fetchPosts` with your API.
 */
export const failNextRequest = createSignal(false);

export function fetchPosts(page: number, signal: AbortSignal): Promise<Post[]> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      if (failNextRequest.value) {
        failNextRequest.value = false;
        reject(new Error('The feed could not be loaded.'));
        return;
      }
      resolve(
        Array.from({ length: kPageSize }, (_, i) => {
          const n = page * kPageSize + i;
          return {
            id: `post-${n}`,
            title: `Post ${n + 1}`,
            image: cdnUrl(kImages[n % kImages.length]),
          };
        }),
      );
    }, kDelayMs);

    signal.addEventListener(
      'abort',
      () => {
        clearTimeout(timer);
        reject(signal.reason);
      },
      { once: true },
    );
  });
}
Los medios de ejemplo solo funcionan en reelkit.dev

cdnUrl crea enlaces a cdn.reelkit.dev, que solo sirve sus archivos a los sitios de ReelKit. En tu app esas imágenes no cargan, así que apunta image a tus propios medios antes de copiar este archivo.

InfiniteFeed.tsx
InfiniteFeed.vue
infinite-feed.component.ts

Desliza hacia arriba o pulsa ↓: una página nueva llega tres publicaciones antes del final. Pulsa "Fail next request" para ver el slide de error y su Retry.

Por qué funciona

  • Crece el recuento, no los slides. count es el número de publicaciones más uno, y en el DOM solo hay tres slides a la vez, así que un feed de miles cuesta lo mismo que uno de diez. Consulta la virtualización.
  • El último slide muestra el estado. El slide extra tras las publicaciones muestra "Loading…" mientras llega una página y el error con Retry cuando ha fallado, así el usuario nunca desliza hacia la nada. Es un caso más del itemBuilder, que dibuja lo que represente cada índice.
  • Pide por adelantado, desde el cambio de slide. afterChange pide la siguiente página cuando el usuario está a tres publicaciones del final. Una página fallida espera a Retry, así que deslizar adelante y atrás no repite una petición que falla. El callback aparece con los demás eventos para React, Vue y Angular.
  • Las claves siguen a las publicaciones. keyExtractor devuelve el id de cada publicación, así un slide conserva su elemento cuando llega una página detrás en vez de reconstruirse desde su índice. Es una prop en React, Vue y Angular por igual.
  • Las imágenes cargan antes que su slide. Un slide más abajo aún no está montado, porque solo lo está el rango visible, como explica la virtualización. Por eso las imágenes de cada página nueva se piden en cuanto llega la página y salen de la caché del navegador cuando su slide se monta.
  • Teclas y rueda apagadas a propósito. enableNavKeys escucha en toda la ventana y enableWheel retiene el scroll de la página mientras el puntero está sobre el feed. Ambos encajan en un feed a pantalla completa y estorban en una página que lo contiene, como esta, así que aquí quedan apagados y dos botones llaman a prev() y next() de la API de Reel. Deslizar funciona igual.
  • Nada sobrevive al feed. Cada petición lleva un AbortController que se aborta cuando el feed se desmonta, así una respuesta tardía nunca escribe en un feed que ya no está.

Siguientes pasos