--- name: relay-fragments-patterns user-invocable: false 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 ```bash # 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 ```javascript // 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 ```javascript // 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 ```javascript // 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 (

{data.title}

{data.excerpt}

{data.author.name} {data.author.name}
); } export default PostCard; ``` ### 2. Fragment Composition ```javascript // 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 (

{data.name}

{data.bio}

); } // UserAvatar.jsx const UserAvatarFragment = graphql` fragment UserAvatar_user on User { name avatar isOnline } `; function UserAvatar({ user }) { const data = useFragment(UserAvatarFragment, user); return (
{data.name} {data.isOnline && }
); } // UserStats.jsx const UserStatsFragment = graphql` fragment UserStats_user on User { postsCount followersCount followingCount } `; function UserStats({ user }) { const data = useFragment(UserStatsFragment, user); return (
Posts: {data.postsCount}
Followers: {data.followersCount}
Following: {data.followingCount}
); } ``` ### 3. Fragment Arguments ```javascript // 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 (

{data.title}

{data.body}
{showComments && ( e.node)} /> )}
); } ``` ### 4. Data Masking ```javascript // 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 (

User ID: {parentData.user.id}

); } // 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 (

{data.name}

{data.email}

{data.name}
); } ``` ### 5. Fragment with Connections ```javascript // 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 (
{data.posts.edges.map(({ node }) => ( ))} {hasNext && ( )}
); } ``` ### 6. Refetchable Fragments ```javascript // 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 (

{data.name}

{data.bio}

); } ``` ### 7. Plural Fragments ```javascript // 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 (
{data.map(post => (

{post.title}

{post.excerpt}

))}
); } // Usage const query = graphql` query PostsQuery { posts { ...PostsList_posts } } `; ``` ### 8. Conditional Fragments ```javascript // 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 (

{data.title}

{data.body}

By {data.author.name}
); case 'Video': return (
); case 'Image': return (
{data.title}
{data.title} by {data.photographer.name}
); default: return null; } } ``` ### 9. Fragment Containers Legacy Pattern ```javascript // Legacy container pattern (v1-11) import { createFragmentContainer, graphql } from 'react-relay'; class PostCard extends React.Component { render() { const { post } = this.props; return (

{post.title}

{post.excerpt}

); } } 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 (

{data.title}

{data.excerpt}

); } ``` ### 10. Advanced Fragment Patterns ```javascript // 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 (

{data.body}

{data.author.name} {data.replies?.edges.map(({ node }) => ( ))}
); } // 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 1. **Colocate fragments with components** - Keep data requirements together 2. **Use fragment composition** - Build complex queries from simple fragments 3. **Leverage data masking** - Prevent tight coupling between components 4. **Define minimal fragments** - Request only necessary fields 5. **Use arguments for flexibility** - Make fragments reusable 6. **Follow naming conventions** - ComponentName_propName pattern 7. **Avoid circular dependencies** - Design fragment hierarchy carefully 8. **Use refetchable fragments** - Enable component-level refetching 9. **Handle loading states** - Provide feedback during data fetches 10. **Type fragments properly** - Ensure type safety with TypeScript ## Common Pitfalls 1. **Fragment overfetching** - Requesting unnecessary fields 2. **Missing data masking** - Accessing non-declared fields 3. **Circular fragment references** - Creating dependency cycles 4. **Improper fragment composition** - Not spreading child fragments 5. **Hardcoded values** - Not using fragment arguments 6. **Breaking data contracts** - Changing fragment fields carelessly 7. **Missing fragment keys** - Not providing unique keys in lists 8. **Ignoring type conditions** - Not handling union types properly 9. **Excessive nesting** - Creating overly deep fragment hierarchies 10. **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 ## Resources - [Relay Documentation](https://relay.dev/) - [Relay Fragments Guide](https://relay.dev/docs/guided-tour/rendering/fragments/) - [Relay Compiler](https://relay.dev/docs/guides/compiler/) - [GraphQL Specification](https://spec.graphql.org/) - [Relay Examples](https://github.com/relayjs/relay-examples) - [Relay Community](https://github.com/facebook/relay/discussions)