| name | relay-fragments-patterns |
| description | Use when relay fragment composition, data masking, colocation, and container patterns for React applications. |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
Relay Fragments Patterns
Master Relay's fragment composition for building maintainable React applications with proper data dependencies and component colocation.
Overview
Relay fragments enable component-level data declaration with automatic composition, data masking, and optimal data fetching. Fragments colocate data requirements with components for better maintainability.
Installation and Setup
Installing Relay
# Install Relay packages
npm install react-relay relay-runtime
# Install Relay compiler
npm install --save-dev relay-compiler babel-plugin-relay
# Install GraphQL
npm install graphql
Relay Configuration
// relay.config.js
module.exports = {
src: './src',
schema: './schema.graphql',
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**'],
language: 'typescript',
artifactDirectory: './src/__generated__'
};
// package.json
{
"scripts": {
"relay": "relay-compiler",
"relay:watch": "relay-compiler --watch"
}
}
Environment Setup
// RelayEnvironment.js
import {
Environment,
Network,
RecordSource,
Store
} from 'relay-runtime';
function fetchQuery(operation, variables) {
return fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
query: operation.text,
variables
})
}).then(response => response.json());
}
const environment = new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource())
});
export default environment;
Core Patterns
1. Basic Fragment Definition
// PostCard.jsx
import { graphql, useFragment } from 'react-relay';
const PostCardFragment = graphql`
fragment PostCard_post on Post {
id
title
excerpt
publishedAt
author {
name
avatar
}
}
`;
function PostCard({ post }) {
const data = useFragment(PostCardFragment, post);
return (
<article>
<h2>{data.title}</h2>
<p>{data.excerpt}</p>
<div>
<img src={data.author.avatar} alt={data.author.name} />
<span>{data.author.name}</span>
</div>
<time>{data.publishedAt}</time>
</article>
);
}
export default PostCard;
2. Fragment Composition
// UserProfile.jsx
const UserProfileFragment = graphql`
fragment UserProfile_user on User {
id
name
bio
...UserAvatar_user
...UserStats_user
}
`;
function UserProfile({ user }) {
const data = useFragment(UserProfileFragment, user);
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
<UserAvatar user={data} />
<UserStats user={data} />
</div>
);
}
// UserAvatar.jsx
const UserAvatarFragment = graphql`
fragment UserAvatar_user on User {
name
avatar
isOnline
}
`;
function UserAvatar({ user }) {
const data = useFragment(UserAvatarFragment, user);
return (
<div className="avatar-container">
<img src={data.avatar} alt={data.name} />
{data.isOnline && <span className="online-indicator" />}
</div>
);
}
// UserStats.jsx
const UserStatsFragment = graphql`
fragment UserStats_user on User {
postsCount
followersCount
followingCount
}
`;
function UserStats({ user }) {
const data = useFragment(UserStatsFragment, user);
return (
<div className="stats">
<div>Posts: {data.postsCount}</div>
<div>Followers: {data.followersCount}</div>
<div>Following: {data.followingCount}</div>
</div>
);
}
3. Fragment Arguments
// Post.jsx
const PostFragment = graphql`
fragment Post_post on Post
@argumentDefinitions(
includeComments: { type: "Boolean!", defaultValue: false }
commentsFirst: { type: "Int", defaultValue: 10 }
) {
id
title
body
comments(first: $commentsFirst) @include(if: $includeComments) {
edges {
node {
...Comment_comment
}
}
}
}
`;
function Post({ post, showComments = false }) {
const data = useFragment(
PostFragment,
post,
{
includeComments: showComments,
commentsFirst: 20
}
);
return (
<article>
<h1>{data.title}</h1>
<div>{data.body}</div>
{showComments && (
<CommentsList comments={data.comments.edges.map(e => e.node)} />
)}
</article>
);
}
4. Data Masking
// Parent component
const ParentFragment = graphql`
fragment Parent_data on Query {
user {
id
...Child_user
}
}
`;
function Parent({ data }) {
const parentData = useFragment(ParentFragment, data);
// parentData.user only contains id and fragment reference
// Cannot access user.name here (data masking)
return (
<div>
<h1>User ID: {parentData.user.id}</h1>
<Child user={parentData.user} />
</div>
);
}
// Child component
const ChildFragment = graphql`
fragment Child_user on User {
name
email
avatar
}
`;
function Child({ user }) {
const data = useFragment(ChildFragment, user);
// Only Child can access name, email, avatar
return (
<div>
<h2>{data.name}</h2>
<p>{data.email}</p>
<img src={data.avatar} alt={data.name} />
</div>
);
}
5. Fragment with Connections
// PostsList.jsx
const PostsListFragment = graphql`
fragment PostsList_query on Query
@argumentDefinitions(
first: { type: "Int", defaultValue: 10 }
after: { type: "String" }
)
@refetchable(queryName: "PostsListRefetchQuery") {
posts(first: $first, after: $after)
@connection(key: "PostsList_posts") {
edges {
node {
id
...PostCard_post
}
}
}
}
`;
function PostsList({ query }) {
const { data, loadNext, hasNext, isLoadingNext } = usePaginationFragment(
PostsListFragment,
query
);
return (
<div>
{data.posts.edges.map(({ node }) => (
<PostCard key={node.id} post={node} />
))}
{hasNext && (
<button
onClick={() => loadNext(10)}
disabled={isLoadingNext}
>
{isLoadingNext ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
6. Refetchable Fragments
// UserProfile.jsx
const UserProfileFragment = graphql`
fragment UserProfile_user on User
@refetchable(queryName: "UserProfileRefetchQuery") {
id
name
bio
posts(first: 10) {
edges {
node {
id
title
}
}
}
}
`;
function UserProfile({ user }) {
const [data, refetch] = useRefetchableFragment(
UserProfileFragment,
user
);
const handleRefresh = () => {
refetch({}, { fetchPolicy: 'network-only' });
};
return (
<div>
<button onClick={handleRefresh}>Refresh</button>
<h1>{data.name}</h1>
<p>{data.bio}</p>
<PostsList posts={data.posts.edges} />
</div>
);
}
7. Plural Fragments
// PostsList.jsx
const PostsListFragment = graphql`
fragment PostsList_posts on Post @relay(plural: true) {
id
title
excerpt
}
`;
function PostsList({ posts }) {
const data = useFragment(PostsListFragment, posts);
return (
<div>
{data.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// Usage
const query = graphql`
query PostsQuery {
posts {
...PostsList_posts
}
}
`;
8. Conditional Fragments
// Content.jsx
const ContentFragment = graphql`
fragment Content_content on Content {
__typename
... on Post {
title
body
author {
name
}
}
... on Video {
title
duration
thumbnailUrl
creator {
name
}
}
... on Image {
title
imageUrl
photographer {
name
}
}
}
`;
function Content({ content }) {
const data = useFragment(ContentFragment, content);
switch (data.__typename) {
case 'Post':
return (
<article>
<h2>{data.title}</h2>
<p>{data.body}</p>
<span>By {data.author.name}</span>
</article>
);
case 'Video':
return (
<div>
<video src={data.thumbnailUrl} />
<h2>{data.title}</h2>
<span>Duration: {data.duration}s</span>
<span>By {data.creator.name}</span>
</div>
);
case 'Image':
return (
<figure>
<img src={data.imageUrl} alt={data.title} />
<figcaption>
{data.title} by {data.photographer.name}
</figcaption>
</figure>
);
default:
return null;
}
}
9. Fragment Containers Legacy Pattern
// Legacy container pattern (v1-11)
import { createFragmentContainer, graphql } from 'react-relay';
class PostCard extends React.Component {
render() {
const { post } = this.props;
return (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
);
}
}
export default createFragmentContainer(PostCard, {
post: graphql`
fragment PostCard_post on Post {
id
title
excerpt
}
`
});
// Modern hooks pattern (v12+)
function PostCard({ post }) {
const data = useFragment(
graphql`
fragment PostCard_post on Post {
id
title
excerpt
}
`,
post
);
return (
<article>
<h2>{data.title}</h2>
<p>{data.excerpt}</p>
</article>
);
}
10. Advanced Fragment Patterns
// Recursive fragments
const CommentFragment = graphql`
fragment Comment_comment on Comment {
id
body
author {
name
}
replies(first: 5) {
edges {
node {
...Comment_comment
}
}
}
}
`;
function Comment({ comment, depth = 0 }) {
const data = useFragment(CommentFragment, comment);
if (depth > 3) return null; // Prevent infinite recursion
return (
<div style={{ marginLeft: depth * 20 }}>
<p>{data.body}</p>
<span>{data.author.name}</span>
{data.replies?.edges.map(({ node }) => (
<Comment key={node.id} comment={node} depth={depth + 1} />
))}
</div>
);
}
// Fragment with inline data
const PostWithInlineFragment = graphql`
fragment PostWithInline_post on Post {
id
title
author {
... on User {
id
name
isVerified
}
... on Organization {
id
name
type
}
}
}
`;
// Fragment with directives
const ConditionalFragment = graphql`
fragment Conditional_post on Post
@argumentDefinitions(
includeLikes: { type: "Boolean!", defaultValue: false }
includeComments: { type: "Boolean!", defaultValue: true }
) {
id
title
likesCount @include(if: $includeLikes)
comments(first: 10) @include(if: $includeComments) {
edges {
node {
id
body
}
}
}
}
`;
// Fragment with required fields
const RequiredFieldsFragment = graphql`
fragment RequiredFields_user on User {
id
name @required(action: LOG)
email @required(action: THROW)
avatar @required(action: NONE)
}
`;
Best Practices
- Colocate fragments with components - Keep data requirements together
- Use fragment composition - Build complex queries from simple fragments
- Leverage data masking - Prevent tight coupling between components
- Define minimal fragments - Request only necessary fields
- Use arguments for flexibility - Make fragments reusable
- Follow naming conventions - ComponentName_propName pattern
- Avoid circular dependencies - Design fragment hierarchy carefully
- Use refetchable fragments - Enable component-level refetching
- Handle loading states - Provide feedback during data fetches
- Type fragments properly - Ensure type safety with TypeScript
Common Pitfalls
- Fragment overfetching - Requesting unnecessary fields
- Missing data masking - Accessing non-declared fields
- Circular fragment references - Creating dependency cycles
- Improper fragment composition - Not spreading child fragments
- Hardcoded values - Not using fragment arguments
- Breaking data contracts - Changing fragment fields carelessly
- Missing fragment keys - Not providing unique keys in lists
- Ignoring type conditions - Not handling union types properly
- Excessive nesting - Creating overly deep fragment hierarchies
- Poor error handling - Not handling missing data gracefully
When to Use
- Building React applications with GraphQL
- Implementing component-driven architecture
- Creating reusable UI components
- Developing data-intensive applications
- Building social media platforms
- Creating e-commerce applications
- Implementing collaborative tools
- Developing content management systems
- Building admin dashboards
- Creating mobile applications with React Native