relay-fragments-patterns

Relay Fragments Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "relay-fragments-patterns" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-relay-fragments-patterns

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;

  1. 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> ); }

  1. 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> ); }

  1. 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> ); }

  1. 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 &#x26;&#x26; (
    &#x3C;button
      onClick={() => loadNext(10)}
      disabled={isLoadingNext}
    >
      {isLoadingNext ? 'Loading...' : 'Load More'}
    &#x3C;/button>
  )}
&#x3C;/div>

); }

  1. 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> ); }

  1. 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 } };

  1. 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 (
    &#x3C;div>
      &#x3C;video src={data.thumbnailUrl} />
      &#x3C;h2>{data.title}&#x3C;/h2>
      &#x3C;span>Duration: {data.duration}s&#x3C;/span>
      &#x3C;span>By {data.creator.name}&#x3C;/span>
    &#x3C;/div>
  );

case 'Image':
  return (
    &#x3C;figure>
      &#x3C;img src={data.imageUrl} alt={data.title} />
      &#x3C;figcaption>
        {data.title} by {data.photographer.name}
      &#x3C;/figcaption>
    &#x3C;/figure>
  );

default:
  return null;

} }

  1. 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> ); }

  1. 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 }) => (
    &#x3C;Comment key={node.id} comment={node} depth={depth + 1} />
  ))}
&#x3C;/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

Resources

  • Relay Documentation

  • Relay Fragments Guide

  • Relay Compiler

  • GraphQL Specification

  • Relay Examples

  • Relay Community

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review