Recipes

Compose the UI with fragments

Let each component declare the columns it renders as a fragment, compose them into one named coverage query, and root the whole screen with useRoot — no request waterfall.

View as Markdown

A fragment is a reusable projection over a table — the columns (and nested relationships) a component renders. Each component declares its own fragment. A named query composes them with .include(...) and .sub(...), and the screen roots them all in one live coverage query. There is no per-component fetch and no waterfall — the whole tree is one subscription.

In production — tantaman.github.io

A card declares exactly what it draws:

// src/components/PostCard.queries.ts — the card's own projection
export const PostCardFragment = defineFragment(post, (p) =>
  p.select(
    "id", "title", "date", "publishedAt", "description", "thesis", "tags",
    "concern", "author", "form", "kind", "cardImage", "color", "pinned", "readingMinutes",
  ),
);
export type PostCardRef = FragmentRef<typeof PostCardFragment>;

rindle-site/src/components/PostCard.queries.ts L17–35 · tantaman.github.io

The list query includes that fragment with .include(...), so the page’s coverage is defined by the components it renders:

export const postsQuery = defineQuery("posts", (raw) => postsArgs.parse(raw), ({ limit }) =>
  q.post
    .orderBy("pinned", "desc")
    .orderBy("publishedAt", "desc")
    .orderBy("id", "asc")
    .limit(limit + 1)
    .include(PostCardFragment),
);

rindle-site/src/components/PostCard.queries.ts L49–56 · tantaman.github.io

Relationships nest with .sub(name, rel, builder) — here a paste with its parent and its forks in one query:

export const pasteQuery = defineQuery("paste", (raw) => pasteIdArgs.parse(raw), (id) =>
  q.paste
    .where.id(id)
    .select("id", "body", "language", "title", "excerpt", "createdAt", "parentId", "shared", "sharedAt")
    .sub("parent", relationships.pasteParent, (parent) =>
      parent.limit(1).select("id", "title", "createdAt", "parentId"),
    )
    .sub("children", relationships.pasteChildren, (child) =>
      child.orderBy("createdAt", "asc").orderBy("id", "asc").limit(PASTE_FORKS_LIMIT)
        .select("id", "title", "createdAt", "parentId"),
    )
    .one(),
);

rindle-site/src/components/Paste.queries.ts L55–70 · tantaman.github.io

The component reads its slice — and only its slice — through useFragment. A parent maps a list of refs, keying each with fragmentKey(post):

// src/components/PostCard.tsx
export function PostCard({ post }: { post: PostCardRef }) {
  const data = useFragment(PostCardFragment, post);
  if (!data) return null;
  const authors = parseList(data.author);
  // …render data.title, authors, data.readingMinutes …
}

rindle-site/src/components/PostCard.tsx L13–19 · tantaman.github.io

Root the screen once with useRoot(postsQuery, …). Every useFragment under it reads from that same live subscription.

See also