Fragments as Composition Units
Understanding why fragments are the atomic building blocks in Relay
Fragments as Composition Units
Master GraphQL Relay fragments with free flashcards and spaced repetition practice. This lesson covers fragments as reusable composition units, container component patterns, and fragment composition hierarchiesβessential concepts for building scalable Relay applications.
Welcome to Fragment-Based Thinking π§©
If you've been thinking of GraphQL queries as monolithic blocks of data requirements, it's time for a paradigm shift. In Relay, fragments are the fundamental building blocks of your data architecture. They're not just query syntaxβthey're composition units that mirror your component structure and enable true component-based data fetching.
Think of fragments like LEGO bricks: each piece is self-contained, reusable, and designed to snap together with other pieces. Just as you wouldn't build a LEGO castle in one giant, unmovable block, you shouldn't write queries as monolithic data requirements. Instead, you compose small, focused fragments that each component declares for itself.
Core Concepts: Why Fragments Are Composition Units π»
The Traditional Query Problem
In traditional GraphQL (without Relay), developers often write queries at the top level that fetch all data for an entire page:
query ProfilePage {
user(id: "123") {
name
email
avatar
bio
followerCount
posts {
id
title
createdAt
likeCount
comments {
id
text
author {
name
avatar
}
}
}
}
}
This approach has critical problems:
- Tight coupling: The parent component must know every data requirement of every child component
- Maintenance nightmare: Changing a nested component requires updating queries far away in the component tree
- No reusability: You can't reuse components without copying their data requirements
- Fragile refactoring: Moving components breaks data fetching
The Relay Solution: Fragments as Units
Relay solves this by making each component declare its own data requirements using fragments:
// UserAvatar.js - declares what IT needs
const UserAvatar = ({ user }) => (
<img src={user.avatar} alt={user.name} />
);
export default createFragmentContainer(UserAvatar, {
user: graphql`
fragment UserAvatar_user on User {
name
avatar
}
`
});
// PostItem.js - declares what IT needs
const PostItem = ({ post }) => (
<div>
<h3>{post.title}</h3>
<UserAvatar user={post.author} />
</div>
);
export default createFragmentContainer(PostItem, {
post: graphql`
fragment PostItem_post on Post {
title
author {
...UserAvatar_user # Compose the child's fragment!
}
}
`
});
Key insight: PostItem doesn't need to know that UserAvatar requires name and avatar. It just spreads the child's fragment using ...UserAvatar_user. This is fragment composition.
The Fragment Container Pattern ποΈ
A fragment container is a component wrapped with its data requirements. The pattern looks like this:
βββββββββββββββββββββββββββββββββββββββ
β Component Logic β
β (render, event handlers, etc.) β
βββββββββββββββββββββββββββββββββββββββ€
β Fragment Declaration β
β graphql`fragment X_y on Type { }` β
βββββββββββββββββββββββββββββββββββββββ€
β createFragmentContainer β
β (connects component to fragment) β
βββββββββββββββββββββββββββββββββββββββ
β
Gets data as props automatically
Relay guarantees that when your component renders, the data specified in its fragment will be available in props. No manual fetching, no loading states at the component levelβthe data is just there.
Fragment Naming Convention π
Relay enforces a strict naming convention:
<ComponentName>_<propName>
Examples:
UserProfile_user- UserProfile component, data passed asuserpropCommentList_comments- CommentList component, data passed ascommentspropPostItem_post- PostItem component, data passed aspostprop
This convention is not optional. Relay uses it to:
- Generate TypeScript/Flow types automatically
- Validate fragment spreads at compile time
- Enable static analysis and refactoring tools
- Make code grep-able and navigable
π‘ Tip: Think of the underscore as "belongs to". UserProfile_user means "the user data that belongs to UserProfile".
Fragment Composition Hierarchy π³
Fragments compose into trees that mirror your component tree:
QUERY (Root)
β
ββ ProfilePage_user
β
ββ UserHeader_user
β β
β ββ UserAvatar_user
β ββ UserBio_user
β
ββ PostList_posts
β
ββ PostItem_post (for each post)
β
ββ PostContent_post
ββ CommentSection_post
β
ββ CommentItem_comment (for each)
Each component only knows about its immediate children's fragments. ProfilePage doesn't know that UserAvatar needs an avatar fieldβit just spreads ...UserHeader_user, which in turn spreads ...UserAvatar_user.
Examples: Fragment Composition in Practice π§
Example 1: Building a User Card Component
Let's build a reusable UserCard from smaller fragments:
// Step 1: Avatar component (leaf node)
const UserAvatar = ({ user }) => (
<img
src={user.avatarUrl}
alt={user.name}
className="rounded-full w-12 h-12"
/>
);
export default createFragmentContainer(UserAvatar, {
user: graphql`
fragment UserAvatar_user on User {
name
avatarUrl
}
`
});
// Step 2: Badge component (leaf node)
const UserBadge = ({ user }) => (
user.isPremium ? <span className="badge">β Premium</span> : null
);
export default createFragmentContainer(UserBadge, {
user: graphql`
fragment UserBadge_user on User {
isPremium
}
`
});
// Step 3: Stats component (leaf node)
const UserStats = ({ user }) => (
<div className="stats">
<span>{user.followerCount} followers</span>
<span>{user.postCount} posts</span>
</div>
);
export default createFragmentContainer(UserStats, {
user: graphql`
fragment UserStats_user on User {
followerCount
postCount
}
`
});
// Step 4: Compose into UserCard (parent node)
const UserCard = ({ user }) => (
<div className="user-card">
<UserAvatar user={user} />
<div>
<h3>{user.name}</h3>
<UserBadge user={user} />
<p>{user.headline}</p>
<UserStats user={user} />
</div>
</div>
);
export default createFragmentContainer(UserCard, {
user: graphql`
fragment UserCard_user on User {
name
headline
...UserAvatar_user # Compose avatar fragment
...UserBadge_user # Compose badge fragment
...UserStats_user # Compose stats fragment
}
`
});
What's happening here?
- Each leaf component (
UserAvatar,UserBadge,UserStats) declares only the fields it renders UserCardcomposes these fragments using the spread operator (...)UserCardalso declares its own direct requirements (name,headline)- The final query sent to the server includes all fields, but components never see each other's data requirements in code
Example 2: List Rendering with Fragments
Handling lists requires understanding how fragments flow through arrays:
// CommentItem.js - renders one comment
const CommentItem = ({ comment }) => (
<div className="comment">
<UserAvatar user={comment.author} />
<div>
<p>{comment.text}</p>
<span>{comment.createdAt}</span>
{comment.canEdit && <button>Edit</button>}
</div>
</div>
);
export default createFragmentContainer(CommentItem, {
comment: graphql`
fragment CommentItem_comment on Comment {
text
createdAt
canEdit
author {
...UserAvatar_user
}
}
`
});
// CommentList.js - renders multiple comments
const CommentList = ({ post }) => (
<div>
<h4>{post.commentCount} Comments</h4>
{post.comments.edges.map(({ node }) => (
<CommentItem key={node.id} comment={node} />
))}
</div>
);
export default createFragmentContainer(CommentList, {
post: graphql`
fragment CommentList_post on Post {
commentCount
comments(first: 10) {
edges {
node {
id
...CommentItem_comment # Fragment spread for each item
}
}
}
}
`
});
Critical insight: The fragment spread ...CommentItem_comment happens inside the node object. This tells Relay: "For each comment node in the connection, fetch the data specified by CommentItem_comment."
Example 3: Conditional Fragment Composition
Sometimes you render different components based on data:
// PostContent.js - handles different post types
const PostContent = ({ post }) => {
switch (post.__typename) {
case 'TextPost':
return <TextPostContent post={post} />;
case 'ImagePost':
return <ImagePostContent post={post} />;
case 'VideoPost':
return <VideoPostContent post={post} />;
default:
return null;
}
};
export default createFragmentContainer(PostContent, {
post: graphql`
fragment PostContent_post on Post {
__typename
... on TextPost {
...TextPostContent_post
}
... on ImagePost {
...ImagePostContent_post
}
... on VideoPost {
...VideoPostContent_post
}
}
`
});
Here we use inline fragments (... on TypeName) to conditionally fetch data based on the concrete type. Relay is smart enough to only fetch the relevant fragment for each item's actual type.
Example 4: Fragment Arguments and Directives
Fragments can accept arguments to customize their behavior:
const UserProfile = ({ user }) => (
<div>
<h1>{user.name}</h1>
<img src={user.profilePicture.uri} />
<PostList posts={user.posts} />
</div>
);
export default createFragmentContainer(UserProfile, {
user: graphql`
fragment UserProfile_user on User
@argumentDefinitions(
pictureSize: { type: "Int", defaultValue: 200 }
includeEmail: { type: "Boolean!", defaultValue: false }
postCount: { type: "Int", defaultValue: 10 }
) {
name
email @include(if: $includeEmail)
profilePicture(size: $pictureSize) {
uri
}
posts(first: $postCount) {
...PostList_posts
}
}
`
});
// Parent component passes arguments when spreading:
const ProfilePage = ({ query }) => (
<UserProfile
user={query.user}
pictureSize={400}
includeEmail={true}
postCount={20}
/>
);
The @argumentDefinitions directive makes fragments configurable. Parent components can pass different values when spreading the fragment, making it even more reusable.
Common Mistakes β οΈ
Mistake 1: Fetching Data the Component Doesn't Use
β Wrong:
const UserName = ({ user }) => <h1>{user.name}</h1>;
export default createFragmentContainer(UserName, {
user: graphql`
fragment UserName_user on User {
name
email # Not used in render!
followerCount # Not used in render!
posts { id } # Not used in render!
}
`
});
β Right:
const UserName = ({ user }) => <h1>{user.name}</h1>;
export default createFragmentContainer(UserName, {
user: graphql`
fragment UserName_user on User {
name # Only what's rendered
}
`
});
Why it matters: Relay's power comes from precise data requirements. Over-fetching defeats the purpose and bloats your queries.
Mistake 2: Skipping the Fragment Container
β Wrong:
// Just a regular component, no fragment
const UserCard = ({ user }) => (
<div>
<img src={user.avatar} />
<h3>{user.name}</h3>
</div>
);
export default UserCard; // No container!
This component can't be composed properly because it doesn't declare its data requirements. Parent components can't know what to fetch.
β
Right: Always wrap with createFragmentContainer and declare a fragment.
Mistake 3: Incorrect Fragment Naming
β Wrong:
export default createFragmentContainer(UserProfile, {
user: graphql`
fragment UserData on User { ... } # Missing component name!
`
});
β Wrong:
export default createFragmentContainer(UserProfile, {
data: graphql`
fragment UserProfile_user on User { ... } # Prop name mismatch!
` // Prop is 'data' but fragment says '_user'
});
β Right:
export default createFragmentContainer(UserProfile, {
user: graphql`
fragment UserProfile_user on User { ... }
` // ComponentName_propName matches!
});
Mistake 4: Forgetting to Spread Child Fragments
β Wrong:
const PostItem = ({ post }) => (
<div>
<h3>{post.title}</h3>
<UserAvatar user={post.author} /> {/* Uses child component */}
</div>
);
export default createFragmentContainer(PostItem, {
post: graphql`
fragment PostItem_post on Post {
title
author {
name # Manually fetching child's data
avatar # This breaks composition!
}
}
`
});
β Right:
const PostItem = ({ post }) => (
<div>
<h3>{post.title}</h3>
<UserAvatar user={post.author} />
</div>
);
export default createFragmentContainer(PostItem, {
post: graphql`
fragment PostItem_post on Post {
title
author {
...UserAvatar_user # Let child declare its needs!
}
}
`
});
Mistake 5: Creating "God Fragments"
β Wrong:
// One fragment to rule them all
const fragment = graphql`
fragment User_all on User {
id
name
email
avatar
bio
followerCount
followingCount
postCount
# ... 50 more fields
}
`;
// Used everywhere, even when only 2 fields are needed
This defeats the entire purpose of fragments. Each component should have its own focused fragment.
β Right: Create multiple small fragments, one per component, and compose them.
Key Takeaways π―
Fragments are composition units, not just syntax. They represent component data dependencies.
Each component declares its own data requirements using fragments. No component should know about its children's or siblings' data needs.
Fragment containers connect components to data. Use
createFragmentContainer(Component, fragments)for every data-driven component.The naming convention is mandatory:
ComponentName_propName. Relay enforces this for good reasons.Compose fragments using the spread operator (
...FragmentName). This is how you build your data dependency tree.Keep fragments minimal. Only fetch fields that the component actually renders. Over-fetching breaks Relay's optimization model.
Fragments mirror your component tree. If you refactor components, the fragments automatically stay consistent.
Use inline fragments (
... on TypeName) for polymorphic types and type-specific data.Make fragments reusable with
@argumentDefinitionswhen you need configurable behavior.Trust the compiler. Relay's compiler validates fragment composition at build time, catching errors before runtime.
π Quick Reference: Fragment Composition Checklist
| β Each component has a fragment container | Use createFragmentContainer |
| β Fragment name follows convention | ComponentName_propName |
| β Fragment only includes used fields | No over-fetching |
| β Child fragments are spread, not duplicated | Use ...ChildFragment_prop |
| β Fragment matches prop name | fragment X_user β prop user |
| β Lists spread fragments in node | edges { node { ...Frag } } |
π§ Try This: Refactoring Exercise
Take an existing component in your project that fetches data at the top level. Try refactoring it:
- Identify all leaf components (components that don't render other data components)
- Create fragment containers for each leaf, declaring only their direct needs
- Work up the tree, creating fragments that spread child fragments
- Replace the top-level query with composed fragments
You'll notice your components become more portable, testable, and maintainable!
π Further Study
Remember: Fragments aren't just a feature of GraphQLβthey're a design pattern for building scalable component architectures. When you think in fragments, you're thinking in composition, reusability, and maintainability. π§©