Skip to content

Fragments ​

Fragments let components declare their own data requirements, keeping your codebase maintainable as it grows.

The Problem with Centralized Queries ​

Without fragments, parent components must know every field their children need:

tsx
const UserProfile = ({ userId }) => {
  const { data } = useQuery(
    graphql(`
      query GetUserQuery($id: ID!) {
        user(id: $id) {
          id
          name
          email
          avatar
          bio
          website
          location
          createdAt
        }
      }
    `),
    { id: userId },
  );

  return (
    <div>
      <UserHeader user={data.user} />
      <UserBio user={data.user} />
      <UserStats user={data.user} />
    </div>
  );
};

The parent queries eight fields. Which component uses which field? You can't tell from the query. This creates several problems:

Hidden Dependencies ​

Components depend on parent queries selecting specific fields. Add a new component or change an existing one, and you must find and update the parent query.

tsx
const UserWebsite = ({ user }) => {
  return <a href={user.website}>{user.website}</a>;
};

This component needs website. But if the parent query doesn't include it, the component breaks. The dependency is invisible.

Over-Fetching ​

To be safe, parent queries request fields "just in case." Remove a component and its fields remain in the query. Over time, queries accumulate unused fields.

Coupling ​

Child components couple tightly to parent queries. Moving a component to a different parent requires updating the new parent's query. Reusing a component means remembering its data requirements.

Refactoring Friction ​

Changing what data a component needs requires finding every query that uses that component and updating them all. This friction discourages refactoring.

Fragment Colocation ​

Fragments solve this by letting components declare their own data requirements:

tsx
const UserWebsite = ({ user }) => {
  const fragment = useFragment(
    graphql(`
      fragment UserWebsite_user on User {
        website
      }
    `),
    user,
  );

  return <a href={fragment.data.website}>{fragment.data.website}</a>;
};

The component explicitly declares: "I need the website field." This declaration lives right next to the code that uses it.

Parents compose fragments instead of listing fields:

tsx
const UserProfile = ({ userId }) => {
  const { data } = useQuery(
    graphql(`
      query GetUserQuery($id: ID!) {
        user(id: $id) {
          ...UserHeader_user
          ...UserBio_user
          ...UserStats_user
          ...UserWebsite_user
        }
      }
    `),
    { id: userId },
  );

  return (
    <div>
      <UserHeader user={data.user} />
      <UserBio user={data.user} />
      <UserStats user={data.user} />
      <UserWebsite user={data.user} />
    </div>
  );
};

The parent doesn't know what fields each fragment includes. It doesn't need to know. Each component manages its own requirements.

Benefits of Colocation ​

Visible Dependencies ​

Data dependencies are explicit. Looking at a component shows exactly what data it needs:

tsx
fragment UserCard_user on User {
  id
  name
  avatar
  email
}

No hunting through parent queries. The fragment documents the component's data contract.

Accurate Fetching ​

Components request exactly the fields they use. Add a field to a component, add it to the fragment. Remove a field from the component, remove it from the fragment. The query matches reality.

Loose Coupling ​

Components become portable. Move UserCard anywhere and its data requirements move with it. The new parent spreads ...UserCard_user and everything works.

Safe Refactoring ​

Change a component's data needs by changing its fragment. TypeScript immediately shows everywhere the component is used. No grepping, no guessing.

How Fragments Work ​

Fragment Definition ​

Define a fragment with your component:

tsx
const UserCard = ({ user }: { user: UserCard_user$key }) => {
  const fragment = useFragment(
    graphql(`
      fragment UserCard_user on User {
        name
        avatar
      }
    `),
    user,
  );

  return (
    <div>
      <img src={fragment.data.avatar} />
      <span>{fragment.data.name}</span>
    </div>
  );
};

The fragment name follows a convention: ComponentName_propName. This makes fragments discoverable and prevents naming collisions.

Fragment Spreading ​

Parent queries spread child fragments:

tsx
const { data } = useQuery(
  graphql(`
    query GetUserQuery($id: ID!) {
      user(id: $id) {
        ...UserCard_user
      }
    }
  `),
  { id: userId },
);

The spread operator ... includes all fields from UserCard_user. GraphQL merges these fields into the final query sent to the server.

Fragment References ​

Props use fragment reference types, not the data directly:

tsx
interface UserCardProps {
  user: UserCard_user$key;
}

The $key suffix indicates a fragment reference. The component must call useFragment to access the data. This enforces the fragment contract at compile time.

Fragment Composition ​

Fragments compose into trees matching your component hierarchy:

tsx
const UserProfile = () => {
  const { data } = useQuery(
    graphql(`
      query GetUserQuery($id: ID!) {
        user(id: $id) {
          ...UserHeader_user
          ...UserContent_user
        }
      }
    `),
    { id },
  );

  return (
    <div>
      <UserHeader user={data.user} />
      <UserContent user={data.user} />
    </div>
  );
};

const UserContent = ({ user }) => {
  const fragment = useFragment(
    graphql(`
      fragment UserContent_user on User {
        ...UserBio_user
        ...UserPosts_user
      }
    `),
    user,
  );

  return (
    <div>
      <UserBio user={fragment.data} />
      <UserPosts user={fragment.data} />
    </div>
  );
};

The hierarchy:

GetUserQuery
├── UserHeader_user
└── UserContent_user
    ├── UserBio_user
    └── UserPosts_user

Each fragment spreads its children's fragments. The final query includes fields from all fragments in the tree.

Type Safety with Fragments ​

Fragment reference types ensure compile-time safety:

Missing Fragments ​

Forget to spread a fragment and TypeScript catches it:

tsx
const { data } = useQuery(
  graphql(`
    query GetUserQuery($id: ID!) {
      user(id: $id) {
        name
      }
    }
  `),
  { id: userId },
);

<UserCard user={data.user} />;

Error: Type 'User' is not assignable to type 'UserCard_user$key'

The error message tells you exactly which fragment is missing.

Extra Fields ​

Fragment types only expose fields declared in the fragment:

tsx
const fragment = useFragment(
  graphql(`
    fragment UserCard_user on User {
      name
      avatar
    }
  `),
  user,
);

console.log(fragment.data.email);

Error: Property 'email' does not exist on type 'UserCard_user'

Components can't access fields they didn't request. This prevents coupling to parent query fields.

Fragment Best Practices ​

Naming Convention ​

Use ComponentName_propName for fragment names:

tsx
fragment UserCard_user on User { ... }
fragment PostList_posts on Post { ... }
fragment CommentThread_comments on Comment { ... }

This makes fragments easy to find and prevents conflicts.

Single Responsibility ​

Each fragment should match one component's needs. Don't create shared fragments used by multiple components. Let each component define its own fragment, even if they request similar fields.

Avoid Fragment Spreading in Queries ​

Query operations should spread fragments, not select fields directly:

tsx
query GetUserQuery($id: ID!) {
  user(id: $id) {
    ...UserProfile_user
  }
}

Not:

tsx
query GetUserQuery($id: ID!) {
  user(id: $id) {
    id
    name
    ...UserProfile_user
  }
}

Let fragments own all field selections.

When Not to Use Fragments ​

Fragments aren't always necessary:

Root Queries ​

Top-level queries that don't pass data to reusable components can select fields directly:

tsx
const { data } = useQuery(
  graphql(`
    query GetPageDataQuery {
      currentUser {
        id
        name
      }
      notifications {
        id
        message
      }
    }
  `),
);

Single-Use Components ​

Components used in exactly one place don't benefit from fragments. The indirection adds complexity without portability benefits.

Simple Data Structures ​

Components displaying very simple data might not need fragments:

tsx
const UserName = ({ name }: { name: string }) => {
  return <span>{name}</span>;
};

This component receives a string, not a fragment reference. Simple props are fine for simple components.

Fragments and Caching ​

Fragments work seamlessly with normalized caching. The cache doesn't distinguish between fields selected directly and fields from fragments. Both normalize the same way:

tsx
fragment UserCard_user on User {
  id
  name
}

Creates the same cache entry as:

tsx
query {
  user {
    id
    name
  }
}

Fragments are a developer experience feature. They don't affect caching behavior or runtime performance.

Next Steps ​