Tying Data to Components with GraphQL Fragments

Giant queries are hard to maintain. When a parent query fetches data for every child component, the whole thing turns brittle: change a child component, and you have to hunt down and update the parent query too.

GraphQL fragments fix this by letting each component own its data requirements.

How it works

With urql and graphql-codegen, you can define a fragment right next to the component that uses it.

1. The Author Card

The AuthorCard needs only a name and an avatar, so that is all its fragment asks for.

// AuthorCard.tsx
export const AuthorFragment = graphql(/* GraphQL */ `
  fragment AuthorDetails on User {
    name
    avatarUrl
  }
`);

export const AuthorCard = (props: { user: FragmentType<typeof AuthorFragment> }) => {
  const user = useFragment(AuthorFragment, props.user);
  return (
    <div>
      <img src={user.avatarUrl} />
      <span>{user.name}</span>
    </div>
  );
};

2. Nesting Fragments

A Comment component needs its own body text, but it also renders an AuthorCard. Rather than redeclare those fields, it simply spreads the author's fragment.

// Comment.tsx
export const CommentFragment = graphql(/* GraphQL */ `
  fragment CommentDetails on Comment {
    body
    author {
      ...AuthorDetails
    }
  }
`);

export const Comment = (props: { comment: FragmentType<typeof CommentFragment> }) => {
  const comment = useFragment(CommentFragment, props.comment);
  return (
    <div>
      <p>{comment.body}</p>
      <AuthorCard user={comment.author} />
    </div>
  );
};

3. The Parent Query

The main blog post query doesn't need to know which fields the author or comments use. It just spreads their fragments and lets each component speak for itself.

// BlogPost.tsx
const PostQuery = graphql(/* GraphQL */ `
  query GetPost($id: ID!) {
    post(id: $id) {
      title
      author { ...AuthorDetails }
      comments { ...CommentDetails }
    }
  }
`);

Why this is better for your workflow

This setup changes how you handle updates.

Change once, update everywhere: To add a "Twitter handle" to the author card, you edit a single file, AuthorCard.tsx.

Automatic updates: The codegen watcher sees the change and propagates it to the parent query, fetching the new field for you. BlogPost.tsx never has to be touched.

Safety: TypeScript won't let you use data you didn't ask for. If the Comment component reaches for the author's bio without declaring it in the fragment, the code simply won't compile.

Less hunting: You never have to dig through files to find where data comes from. It's always defined right where it's used.

That's the whole idea: your components stay independent and your queries stay clean. You can find the full setup in the urql documentation.