v0 · Developer Preview Frond is under active development. APIs may change between releases.

Pagination

Make the cursor part of the args, and each page becomes its own node with its own id. Pages cache by identity: revisiting a page reads the existing node, and prefetching the next is a single acquire.

One node per page

type FeedPageResult = {
  items: FeedItem[];
  nextCursor: string | null;
};

type FeedPageSpec = Frond.NodeSpec<{
  args: { cursor: string | null };
  key: Frond.Key.Structure<{ cursor: string | null }>;
  result: FeedPageResult;
}>;

export class FeedPageNode extends Frond.NodeBase<FeedPageSpec> {
  static readonly spec = Frond.resourceSpec<FeedPageSpec>({
    tag: Frond.tag("resources/feed-page"),
    key: (args) => Frond.Key.structure({ cursor: args.cursor }),
    driver: Frond.Driver.Async<FeedPageSpec>({
      acquire: Frond.Driver.Acquire((ctx) => fetchFeedPage(ctx.args.cursor)),
    }),
  });
}

The cursor is in the key, so { cursor: null } and { cursor: "abc" } are different nodes. Each loads once and stays cached until evicted. See Identity and keys.

Render the current page

Hold the current cursor in component state and read its node.

const FeedPage = observer(({ cursor }: { cursor: string | null }) => {
  const page = FrondReact.useNode(FeedPageNode, { cursor });
  return (
    <>
      <FeedList items={page.result.items} />
      <NextButton cursor={page.result.nextCursor} />
    </>
  );
});

Moving back to a page you already loaded resolves to the cached node — no refetch.

Prefetch the next page

Acquire the next page before the user asks for it. Outside React, acquire through the client; the node is ready by the time you navigate.

function prefetch(cursor: string) {
  void runtime.client.node(FeedPageNode, { cursor }).ensureReady();
}

In React, Preload acquires a page before rendering it, and useNodeControls can evict pages you no longer want cached.


Next: Auth-aware nodes.