Stop Thinking in Queries (Think in Fragments)
Unlearn traditional data fetching patterns and embrace fragment-based composition as the foundation of Relay's architecture
Stop Thinking in Queries (Think in Fragments)
Master Relay's fragment-based architecture with free flashcards and spaced repetition practice. This lesson covers the mental shift from query-centric to fragment-centric thinking, colocation of data requirements, and building composable UI componentsβessential concepts for building scalable React applications with GraphQL.
Welcome to Fragment-First Development π»
If you're coming from traditional REST APIs or even basic GraphQL implementations, you're probably used to thinking about data fetching at the page or container level. You write queries, fetch all the data upfront, and pass props down through your component tree. Relay fundamentally changes this paradigm.
Instead of thinking "What data does this page need?", Relay teaches you to ask "What data does this specific component need?" This shiftβfrom queries to fragmentsβis the most important mental model change you'll make when learning Relay.
Why This Shift Matters π―
Traditional data fetching creates tight coupling between components and their containers:
<pre> β TRADITIONAL APPROACH (Query-Centric)
βββββββββββββββββββββββββββββββββββββββ β PageContainer β β β β query { β β user { id, name, email, avatar } β β posts { id, title, content } β β comments { id, text, author } β β } β β β β βββββββββββββββββββββ β β β UserProfile β β β β props: user β β β βββββββββββββββββββββ β β β β βββββββββββββββββββββ β β β PostList β β β β props: posts β β β βββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββ
β οΈ Problems: β’ Parent must know ALL child data needs β’ Components can't be reused easily β’ Changing a component breaks the parent </pre>
With Relay's fragment-based approach, each component declares its own data requirements:
<pre> β RELAY APPROACH (Fragment-Centric)
βββββββββββββββββββββββββββββββββββββββ β PageContainer β β β β query { β β user { β β ...UserProfile_user β β } β β posts { β β ...PostList_posts β β } β β } β β β β βββββββββββββββββββββββββ β β β UserProfile β β β β β β β β fragment on User { β β β β id, name, avatar β β β β } β β β βββββββββββββββββββββββββ β β β β βββββββββββββββββββββββββ β β β PostList β β β β β β β β fragment on Post { β β β β id, title β β β β } β β β βββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββ
β Benefits: β’ Components are self-contained β’ Easy to move/reuse components β’ Parent doesn't need to know details </pre>
The Fragment Contract π
A fragment is a named piece of a GraphQL query that specifies data requirements for a single component. Think of it as a contract:
"If you give me a User object, I promise to only read these specific fields from it."
Basic Fragment Syntax
import { graphql } from 'react-relay';
const UserProfile = ({ user }) => (
<div>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
export default createFragmentContainer(UserProfile, {
user: graphql`
fragment UserProfile_user on User {
name
email
avatar
}
`
});
Breaking this down:
fragment UserProfile_user- The fragment name (convention:ComponentName_propName)on User- The GraphQL type this fragment applies to{ name, email, avatar }- The exact fields this component needs
π‘ Naming Convention Tip: Always use ComponentName_propName format. This makes it clear which component owns which fragment and prevents naming collisions.
Colocation: Data Next to Code π
The most powerful aspect of fragments is colocationβkeeping data requirements right next to the code that uses them. This creates several advantages:
1. Self-Documenting Components
When you look at a component, you immediately see what data it needs:
// Everything you need to know is right here!
const CommentCard = ({ comment }) => (
<div className="comment">
<span>{comment.author.name}</span>
<p>{comment.text}</p>
<time>{comment.createdAt}</time>
</div>
);
export default createFragmentContainer(CommentCard, {
comment: graphql`
fragment CommentCard_comment on Comment {
text
createdAt
author {
name
}
}
`
});
2. Safe Refactoring
Need to display the author's avatar? Just add it to the fragment:
fragment CommentCard_comment on Comment {
text
createdAt
author {
name
avatar # β Add this
}
}
Relay automatically updates all queries that use this fragment. No hunting through parent components!
3. Component Reusability
You can drop a component anywhere in your app. As long as the parent includes the fragment, it works:
// Works in UserPage
query UserPageQuery {
user(id: $userId) {
...CommentCard_comment
}
}
// Also works in PostPage
query PostPageQuery {
post(id: $postId) {
comments {
...CommentCard_comment
}
}
}
// Also works in NotificationFeed
query NotificationsQuery {
notifications {
comment {
...CommentCard_comment
}
}
}
Composing Fragments π§©
Fragments compose just like React components compose. A parent component includes child fragments:
const PostCard = ({ post }) => (
<article>
<h2>{post.title}</h2>
<AuthorBadge author={post.author} />
<p>{post.excerpt}</p>
<CommentList comments={post.comments} />
</article>
);
export default createFragmentContainer(PostCard, {
post: graphql`
fragment PostCard_post on Post {
title
excerpt
author {
...AuthorBadge_author
}
comments {
...CommentList_comments
}
}
`
});
Notice:
PostCarddoesn't know whatAuthorBadgeneedsβit just spreads the fragmentPostCarddoesn't know whatCommentListneedsβit just spreads the fragment- Each component is encapsulated and independent
<pre> FRAGMENT COMPOSITION TREE
PostCard_post
β
βββββββββ΄βββββββββ
β β
βΌ βΌ
AuthorBadge_author CommentList_comments β β βΌ βΌ (name, avatar) ββββ΄βββ β β βΌ βΌ CommentCard_comment β βΌ (text, author, time) </pre>
Example 1: Building a User Dashboard π€
Let's build a user dashboard using fragments. We'll start with individual components and compose them:
Step 1: The Avatar Component
const Avatar = ({ user }) => (
<img
src={user.profilePicture}
alt={user.name}
className="avatar"
/>
);
export default createFragmentContainer(Avatar, {
user: graphql`
fragment Avatar_user on User {
name
profilePicture
}
`
});
Step 2: The Stats Component
const UserStats = ({ user }) => (
<div className="stats">
<div>
<strong>{user.followerCount}</strong>
<span>Followers</span>
</div>
<div>
<strong>{user.followingCount}</strong>
<span>Following</span>
</div>
<div>
<strong>{user.postCount}</strong>
<span>Posts</span>
</div>
</div>
);
export default createFragmentContainer(UserStats, {
user: graphql`
fragment UserStats_user on User {
followerCount
followingCount
postCount
}
`
});
Step 3: The Dashboard (Composing Everything)
const UserDashboard = ({ user }) => (
<div className="dashboard">
<Avatar user={user} />
<h1>{user.displayName}</h1>
<p>{user.bio}</p>
<UserStats user={user} />
</div>
);
export default createFragmentContainer(UserDashboard, {
user: graphql`
fragment UserDashboard_user on User {
displayName
bio
...Avatar_user
...UserStats_user
}
`
});
What the final query looks like:
query DashboardPageQuery($userId: ID!) {
user(id: $userId) {
...UserDashboard_user
}
}
Relay automatically expands this to:
query DashboardPageQuery($userId: ID!) {
user(id: $userId) {
displayName
bio
name # from Avatar_user
profilePicture # from Avatar_user
followerCount # from UserStats_user
followingCount # from UserStats_user
postCount # from UserStats_user
}
}
π§ Try this: Add a new component UserBadges that shows achievement badges. You only need to:
- Create the component with its fragment
- Add
...UserBadges_usertoUserDashboard_user - Render
<UserBadges user={user} />in the JSX
No changes to the page query needed!
Example 2: Nested Fragments with Lists π
Let's build a blog feed that demonstrates fragments with collections:
The Author Badge (Reusable)
const AuthorBadge = ({ author }) => (
<div className="author">
<img src={author.avatar} alt="" />
<span>{author.name}</span>
{author.isVerified && <VerifiedIcon />}
</div>
);
export default createFragmentContainer(AuthorBadge, {
author: graphql`
fragment AuthorBadge_author on User {
name
avatar
isVerified
}
`
});
The Post Summary
const PostSummary = ({ post }) => (
<article>
<h3>{post.title}</h3>
<AuthorBadge author={post.author} />
<p>{post.excerpt}</p>
<div className="meta">
<span>{post.likeCount} likes</span>
<span>{post.commentCount} comments</span>
<time>{post.publishedAt}</time>
</div>
</article>
);
export default createFragmentContainer(PostSummary, {
post: graphql`
fragment PostSummary_post on Post {
title
excerpt
likeCount
commentCount
publishedAt
author {
...AuthorBadge_author
}
}
`
});
The Feed (List of Posts)
const FeedList = ({ posts }) => (
<div className="feed">
{posts.map(post => (
<PostSummary key={post.id} post={post} />
))}
</div>
);
export default createFragmentContainer(FeedList, {
posts: graphql`
fragment FeedList_posts on Post @relay(plural: true) {
id
...PostSummary_post
}
`
});
π‘ Notice @relay(plural: true): This directive tells Relay that posts is an array, not a single object. Always use this for list fragments.
The Page Query
query FeedPageQuery {
posts(first: 10) {
edges {
node {
...FeedList_posts
}
}
}
}
<div style="border: 2px solid #4fd1c5; border-radius: 8px; padding: 16px; margin: 16px 0; background: rgba(79, 209, 197, 0.1);"> <h4>π§ Memory Device: The Matryoshka Doll Principle</h4> <p>Think of fragments like Russian nesting dolls (matryoshkas). Each doll (component) has its own complete shape (fragment), but they fit inside each other. You don't need to know what's inside the inner dollsβyou just know they fit.</p> <pre> βββββββββββββββββββββββββββ β FeedList_posts β β βββββββββββββββββββββ β β β PostSummary_post β β β β βββββββββββββββ β β β β βAuthorBadge β β β β β β _author β β β β β βββββββββββββββ β β β βββββββββββββββββββββ β βββββββββββββββββββββββββββ </pre> </div>
Example 3: Conditional Fragments ποΈ
Sometimes you need different data based on the GraphQL type. Use inline fragments for polymorphism:
const FeedItem = ({ item }) => {
switch (item.__typename) {
case 'Post':
return <PostCard post={item} />;
case 'Photo':
return <PhotoCard photo={item} />;
case 'Video':
return <VideoCard video={item} />;
default:
return null;
}
};
export default createFragmentContainer(FeedItem, {
item: graphql`
fragment FeedItem_item on FeedItemInterface {
__typename
... on Post {
...PostCard_post
}
... on Photo {
...PhotoCard_photo
}
... on Video {
...VideoCard_video
}
}
`
});
Key points:
__typenameis automatically availableβit tells you the concrete type... on Typeis an inline fragment for a specific type- Each type can have completely different fields
<pre> POLYMORPHIC FEED STRUCTURE
FeedItemInterface
β
ββββββββΌβββββββ
β β β
βΌ βΌ βΌ
Post Photo Video β β β βΌ βΌ βΌ (title) (url) (duration) </pre>
Example 4: Arguments in Fragments ποΈ
Fragments can accept arguments, but they must be defined in the parent query:
const UserProfile = ({ user }) => (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
<img src={user.profilePicture} alt="Profile" />
</div>
);
export default createFragmentContainer(UserProfile, {
user: graphql`
fragment UserProfile_user on User
@argumentDefinitions(
size: { type: "Int", defaultValue: 200 }
) {
name
bio
profilePicture(size: $size)
}
`
});
Using it in a query:
query ProfilePageQuery($userId: ID!) {
user(id: $userId) {
...UserProfile_user @arguments(size: 400)
}
}
π‘ When to use arguments:
- Image sizes (thumbnails vs. full-size)
- Pagination limits (
first: 10vs.first: 50) - Date formatting
- Sorting options
β οΈ Keep it simple: Most fragments don't need arguments. Use them sparingly for truly reusable components that need flexibility.
Common Mistakes and How to Avoid Them β οΈ
Mistake 1: Thinking Like REST
β Wrong:
// Fetching everything in the container
const PageContainer = () => {
const data = useQuery(graphql`
query PageQuery {
user { id, name, email, avatar, bio, ... }
posts { id, title, content, author, ... }
}
`);
return (
<>
<UserProfile user={data.user} />
<PostList posts={data.posts} />
</>
);
};
β Right:
// Components declare their needs
const PageContainer = () => {
const data = useQuery(graphql`
query PageQuery {
user { ...UserProfile_user }
posts { ...PostList_posts }
}
`);
return (
<>
<UserProfile user={data.user} />
<PostList posts={data.posts} />
</>
);
};
Mistake 2: Not Spreading Child Fragments
β Wrong:
fragment PostCard_post on Post {
title
author {
name # β Directly querying child fields
avatar
}
}
β Right:
fragment PostCard_post on Post {
title
author {
...AuthorBadge_author # β Spreading child fragment
}
}
Why it matters: If AuthorBadge needs more fields later (like isVerified), the wrong approach requires updating PostCard_post. The right approach handles it automatically.
Mistake 3: Over-Fetching in Fragments
β Wrong:
fragment Avatar_user on User {
id
name
email # β Not used in component
bio # β Not used in component
followerCount # β Not used in component
avatar
}
β Right:
fragment Avatar_user on User {
name # Used in alt text
avatar # Used in src
}
Principle: Only request fields you actually render or use in logic. Every field has a cost.
Mistake 4: Missing @relay(plural: true)
β Wrong:
fragment CommentList_comments on Comment {
id
text
}
This will cause runtime errors when you try to map over comments.
β Right:
fragment CommentList_comments on Comment @relay(plural: true) {
id
text
}
Mistake 5: Inconsistent Naming
β Wrong:
// Random names
fragment userFrag on User { ... }
fragment Post_Fragment on Post { ... }
fragment commentData_stuff on Comment { ... }
β Right:
// Consistent ComponentName_propName pattern
fragment UserProfile_user on User { ... }
fragment PostCard_post on Post { ... }
fragment CommentList_comments on Comment { ... }
<table> <tr><th>Anti-Pattern</th><th>Consequence</th><th>Fix</th></tr> <tr><td>Querying in container</td><td>Tight coupling</td><td>Use fragments</td></tr> <tr><td>Not spreading children</td><td>Brittle updates</td><td>Always spread</td></tr> <tr><td>Fetching unused fields</td><td>Slow queries</td><td>Minimal fragments</td></tr> <tr><td>Missing plural directive</td><td>Runtime errors</td><td>Add @relay(plural: true)</td></tr> <tr><td>Inconsistent names</td><td>Confusion</td><td>ComponentName_propName</td></tr> </table>
The Mental Model Shift: Before and After π§
Before: Query-Centric Thinking
<pre> "I need to build a profile page. What data does this page need?" β βΌ βββββββββββββββββββββββββββββββββββ β Write big query with all data β βββββββββββββββββββββββββββββββββββ β βΌ βββββββββββββββββββββββββββββββββββ β Pass data down as props β βββββββββββββββββββββββββββββββββββ β βΌ βββββββββββββββββββββββββββββββββββ β Components are prop-dependent β βββββββββββββββββββββββββββββββββββ
β οΈ Result: Tightly coupled system </pre>
After: Fragment-Centric Thinking
<pre> "I need to build a profile page. What components does this page have?" β βΌ βββββββββββββββββββββββββββββββββββ β Build each component with β β its own fragment β βββββββββββββββββββββββββββββββββββ β βΌ βββββββββββββββββββββββββββββββββββ β Compose fragments in parent β βββββββββββββββββββββββββββββββββββ β βΌ βββββββββββββββββββββββββββββββββββ β Write minimal page query that β β just spreads root fragment β βββββββββββββββββββββββββββββββββββ
β Result: Loosely coupled, composable system </pre>
Key Principles to Remember π―
- Fragments over Queries: Think about component data needs, not page data needs
- Colocation: Keep data requirements next to the code that uses them
- Composition: Parent components spread child fragments, don't duplicate them
- Encapsulation: Components shouldn't know about their children's data needs
- Minimal Fetching: Only request fields you actually use
π€ Did you know? Facebook's codebase has tens of thousands of fragments. The largest queries are automatically composed from hundreds of nested fragments. This system scales because no single developer needs to understand the entire data graphβjust their component's fragment.
<div style="border: 2px solid #4fd1c5; border-radius: 8px; padding: 16px; margin: 16px 0; background: rgba(79, 209, 197, 0.1);"> <h4>π Quick Reference Card: Query vs. Fragment Mindset</h4> <table> <tr> <th>Query Mindset β</th> <th>Fragment Mindset β </th> </tr> <tr> <td>"What does the page need?"</td> <td>"What does this component need?"</td> </tr> <tr> <td>Container knows all child data</td> <td>Each component declares its own data</td> </tr> <tr> <td>Props passed down from query</td> <td>Fragments composed up to query</td> </tr> <tr> <td>Change component β update parent</td> <td>Change component β fragment updates</td> </tr> <tr> <td>Hard to move/reuse components</td> <td>Easy to move/reuse components</td> </tr> <tr> <td>Tight coupling</td> <td>Loose coupling</td> </tr> </table>
<h4>Fragment Syntax Cheatsheet</h4> <table> <tr><td><strong>Basic fragment</strong></td><td><code>fragment Name_prop on Type </code></td></tr> <tr><td><strong>Spread fragment</strong></td><td><code>...ComponentName_prop</code></td></tr> <tr><td><strong>Plural/array</strong></td><td><code>@relay(plural: true)</code></td></tr> <tr><td><strong>With arguments</strong></td><td><code>@argumentDefinitions(arg: {type: "Type"})</code></td></tr> <tr><td><strong>Inline fragment</strong></td><td><code>... on ConcreteType </code></td></tr> <tr><td><strong>Naming convention</strong></td><td><code>ComponentName_propName</code></td></tr> </table> </div>
Practical Exercise: Refactor a Query-Based Component π§
Let's practice the mental shift. Here's a traditional query-based component:
Before (Query-Centric):
const BlogPost = ({ postId }) => {
const data = useQuery(graphql`
query BlogPostQuery($postId: ID!) {
post(id: $postId) {
id
title
content
publishedAt
author {
id
name
avatar
bio
}
comments {
id
text
createdAt
author {
id
name
avatar
}
}
}
}
`);
return (
<article>
<h1>{data.post.title}</h1>
<div className="author">
<img src={data.post.author.avatar} />
<span>{data.post.author.name}</span>
<p>{data.post.author.bio}</p>
</div>
<div>{data.post.content}</div>
<div className="comments">
{data.post.comments.map(comment => (
<div key={comment.id}>
<img src={comment.author.avatar} />
<span>{comment.author.name}</span>
<p>{comment.text}</p>
<time>{comment.createdAt}</time>
</div>
))}
</div>
</article>
);
};
After (Fragment-Centric):
// Step 1: Extract AuthorCard component
const AuthorCard = ({ author }) => (
<div className="author">
<img src={author.avatar} alt="" />
<span>{author.name}</span>
<p>{author.bio}</p>
</div>
);
export default createFragmentContainer(AuthorCard, {
author: graphql`
fragment AuthorCard_author on User {
name
avatar
bio
}
`
});
// Step 2: Extract Comment component
const Comment = ({ comment }) => (
<div className="comment">
<img src={comment.author.avatar} alt="" />
<span>{comment.author.name}</span>
<p>{comment.text}</p>
<time>{comment.createdAt}</time>
</div>
);
export default createFragmentContainer(Comment, {
comment: graphql`
fragment Comment_comment on Comment {
text
createdAt
author {
name
avatar
}
}
`
});
// Step 3: Extract CommentList component
const CommentList = ({ comments }) => (
<div className="comments">
{comments.map(comment => (
<Comment key={comment.id} comment={comment} />
))}
</div>
);
export default createFragmentContainer(CommentList, {
comments: graphql`
fragment CommentList_comments on Comment @relay(plural: true) {
id
...Comment_comment
}
`
});
// Step 4: Main BlogPost component
const BlogPost = ({ post }) => (
<article>
<h1>{post.title}</h1>
<AuthorCard author={post.author} />
<div>{post.content}</div>
<CommentList comments={post.comments} />
</article>
);
export default createFragmentContainer(BlogPost, {
post: graphql`
fragment BlogPost_post on Post {
title
content
author {
...AuthorCard_author
}
comments {
...CommentList_comments
}
}
`
});
// Step 5: Minimal page query
const BlogPostPage = ({ postId }) => {
const data = useQuery(graphql`
query BlogPostPageQuery($postId: ID!) {
post(id: $postId) {
...BlogPost_post
}
}
`);
return <BlogPost post={data.post} />;
};
Benefits of the refactor:
- β
AuthorCardcan be reused anywhere - β
Commentcan be reused in notifications, activity feeds, etc. - β
Changing
Commentdoesn't require touchingBlogPost - β Each component is self-documenting
- β Easy to test components in isolation
Key Takeaways π
Stop writing big queries at the top β Start with fragments in leaf components
Fragments are contracts β "Give me this type, I'll only read these fields"
Colocation is powerful β Data requirements live with the code that uses them
Composition over duplication β Spread child fragments, don't duplicate their fields
Think bottom-up β Build components with fragments first, compose into queries last
Encapsulation enables reuse β Components that declare their own data are easier to move around
Follow naming conventions β
ComponentName_propNamekeeps things organizedUse directives appropriately β
@relay(plural: true)for arrays,@argumentDefinitionsfor flexibilityKeep fragments minimal β Only request what you render
Trust the system β Relay handles query composition, deduplication, and optimization for you
π Further Study
- Relay Fragments Documentation β Official guide to fragment fundamentals
- Thinking in Relay β Deep dive into Relay's mental model
- GraphQL Fragments Best Practices β Cross-framework fragment patterns
Next Steps: Now that you understand fragments, you're ready to learn about Relay's data-driven rendering system and how fragments integrate with React Suspense. The fragment-first mindset you've developed here is the foundation for everything else in Relay!