Docs
Query
Paginated/Infinite Query

Paginated or Infinite Query

Creating the Store/Hook

Rendering list of items with a "load more" feature is a very common UI pattern. To achieve this "load more" functionality, we can set the getNextPageParam option to determine what next offset or cursor is, and then use the last page param on query function. Lastly, we need to utilize select option to concat the previous data with the new response.

type PokemonsResponse = { results: Pokemon[]; success: boolean }
type Pokemon = { id: number; name: string };
 
const usePokemonsQuery = createQuery<undefined, PokemonsResponse, Pokemon[]>(
  //                                 ^key       ^response         ^data (from select)
  async (_, { pageParam = 0 }) => {
    const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10&offset=${pageParam}`);
    if (res.ok) return res.json();
    throw res;
  },
  {
    select: (response, { data = [] }) => [...data, ...response.results],
    getNextPageParam: ({ pagination }) => (
      pagination.totalPages > pagination.currentPage ? (pagination.currentPage + 1) : undefined;
    ),
    // Or if we use cursor:
    // getNextPageParam: (lastPageResponse) => lastPageResponse.cursor,
  },
);

In order to tell Floppy Disk that we already reached at the end of page, return undefined or null on getNextPageParam. After doing that, the hasNextPage will be true and the fetchNextPage will no longer triggering anything.

Using Query Hook Inside a Component

function PokemonListPage() {
  const { data, fetchNextPage, hasNextPage, isWaitingNextPage } = usePokemonsQuery();
 
  return (
    <div>
      {data?.map((pokemon) => (
        <div key={pokemon.name}>{pokemon.name}</div>
      ))}
 
      {isWaitingNextPage ? (
        <div>Loading more...</div>
      ) : (
        hasNextPage && <button onClick={fetchNextPage}>Load more</button>
      )}
    </div>
  );
}

Bi-Directional Infinite List

Creating bi-directional query is possible by creating 2 infinite queries and merge its data together. However, we must prevent fetching the initial page 2 times and handle 2 queries refetch simultaneously. Therefore, Floppy Disk provide an abstraction to do that using createBiDirectionQuery.

import { createBiDirectionQuery } from 'floppy-disk';
 
const fetchProjects = async (cursor) => {
  const res = await fetch(`/api/projects?cursor=${cursor}`);
  const resJson = await res.json();
  if (res.ok) return resJson;
  throw resJson;
};
 
const useProjectsQuery = createBiDirectionQuery(
  (queryKey, { pageParam }, direction) => fetchProjects(pageParam || 0),
  //                        ^? (parameter) direction: "prev" | "next"
  {
    select: (response, { data = [] }, direction) => {
      return direction === 'prev' ? response.data.concat(data) : data.concat(response.data);
    },
    getPrevPageParam: (response) => response.previousId,
    getNextPageParam: (response) => response.nextId,
  },
);
 
function MyPage() {
  const {
    data,
    fetchPrevPage, hasPrevPage, isWaitingPrevPage,
    fetchNextPage, hasNextPage, isWaitingNextPage,
  } = useProjectsQuery();
 
  ...
}