Skip to content
relay-fragments-patterns logo

Relay Fragments Patterns

relay-fragments-patterns

Use when relay fragment composition, data masking, colocation, and container patterns for React applications.

TheBushidoCollective/han0installs199stars

SKILL.md

Full skill instructions

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

  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

More skills from TheBushidoCollective

typescript-utility-types logo
TheBushidoCollective/han

typescript-utility-types

Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.

199 0
View
shell-portability logo
TheBushidoCollective/han

shell-portability

Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.

199 0
View
tailwind-performance logo
TheBushidoCollective/han

tailwind-performance

Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.

199 0
View
figma-generate-component logo
TheBushidoCollective/han

figma-generate-component

Generate production-ready code from a Figma component or frame using the Figma MCP server

199 0
View
dialyzer-integration logo
TheBushidoCollective/han

dialyzer-integration

Use when integrating Dialyzer into development workflows and CI/CD pipelines for Erlang/Elixir projects.

199 0
View
rubocop-configuration logo
TheBushidoCollective/han

rubocop-configuration

Use when ruboCop configuration including YAML config, departments, inheritance, and customization.

199 0
View
elixir-otp-patterns logo
TheBushidoCollective/han

elixir-otp-patterns

Use when Elixir OTP patterns including GenServer, Supervisor, Agent, and Task. Use when building concurrent, fault-tolerant Elixir applications.

199 0
View
ameba-custom-rules logo
TheBushidoCollective/han

ameba-custom-rules

Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.

199 0
View
Swift Optionals Patterns logo
TheBushidoCollective/han

Swift Optionals Patterns

Use when swift's optional handling patterns including optional binding, chaining, nil coalescing, and modern approaches to safely working with optional values while avoiding common pitfalls and force unwrapping.

199 0
View
code-annotation-patterns logo
TheBushidoCollective/han

code-annotation-patterns

Use when annotating code with structured metadata, tags, and markers for AI-assisted development workflows. Covers annotation formats, semantic tags, and integration with development tools.

199 0
View
Scala Collections logo
TheBushidoCollective/han

Scala Collections

Use when scala collections including immutable/mutable variants, List, Vector, Set, Map operations, collection transformations, lazy evaluation with views, parallel collections, and custom collection builders for efficient data processing.

199 0
View
reactflow-custom-nodes logo
TheBushidoCollective/han

reactflow-custom-nodes

Use when creating custom React Flow nodes, edges, and handles. Covers custom node components, resizable nodes, toolbars, and advanced customization.

199 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View