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:
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.
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:
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:
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:
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:
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:
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:
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:
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_userEach 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:
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:
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:
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:
query GetUserQuery($id: ID!) {
user(id: $id) {
...UserProfile_user
}
}Not:
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:
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:
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:
fragment UserCard_user on User {
id
name
}Creates the same cache entry as:
query {
user {
id
name
}
}Fragments are a developer experience feature. They don't affect caching behavior or runtime performance.
Next Steps
- Modern GraphQL - How fragments fit into modern GraphQL architecture
- Type Safety - How fragment types provide compile-time safety
- Using Fragments - Practical guide to using fragments
- Fragments Guide - Advanced fragment patterns and techniques