ð relay-fragments-patterns
Use when relay fragment composition, data masking, colocation, and container patterns for React applications.
Overview
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