You are viewing a preview of this lesson. Sign in to start learning
Back to Mastering Relay

Stop Thinking in Queries (Think in Fragments)

Unlearn traditional data fetching patterns and embrace fragment-based composition as the foundation of Relay's architecture

Last generated

Stop Thinking in Queries (Think in Fragments)

Master Relay's fragment-based architecture with free flashcards and spaced repetition practice. This lesson covers the mental shift from query-centric to fragment-centric thinking, colocation of data requirements, and building composable UI componentsβ€”essential concepts for building scalable React applications with GraphQL.

Welcome to Fragment-First Development πŸ’»

If you're coming from traditional REST APIs or even basic GraphQL implementations, you're probably used to thinking about data fetching at the page or container level. You write queries, fetch all the data upfront, and pass props down through your component tree. Relay fundamentally changes this paradigm.

Instead of thinking "What data does this page need?", Relay teaches you to ask "What data does this specific component need?" This shiftβ€”from queries to fragmentsβ€”is the most important mental model change you'll make when learning Relay.

Why This Shift Matters 🎯

Traditional data fetching creates tight coupling between components and their containers:

<pre> ❌ TRADITIONAL APPROACH (Query-Centric)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PageContainer β”‚ β”‚ β”‚ β”‚ query { β”‚ β”‚ user { id, name, email, avatar } β”‚ β”‚ posts { id, title, content } β”‚ β”‚ comments { id, text, author } β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ UserProfile β”‚ β”‚ β”‚ β”‚ props: user β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ PostList β”‚ β”‚ β”‚ β”‚ props: posts β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⚠️ Problems: β€’ Parent must know ALL child data needs β€’ Components can't be reused easily β€’ Changing a component breaks the parent </pre>

With Relay's fragment-based approach, each component declares its own data requirements:

<pre> βœ… RELAY APPROACH (Fragment-Centric)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PageContainer β”‚ β”‚ β”‚ β”‚ query { β”‚ β”‚ user { β”‚ β”‚ ...UserProfile_user β”‚ β”‚ } β”‚ β”‚ posts { β”‚ β”‚ ...PostList_posts β”‚ β”‚ } β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ UserProfile β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ fragment on User { β”‚ β”‚ β”‚ β”‚ id, name, avatar β”‚ β”‚ β”‚ β”‚ } β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ PostList β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ fragment on Post { β”‚ β”‚ β”‚ β”‚ id, title β”‚ β”‚ β”‚ β”‚ } β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

βœ… Benefits: β€’ Components are self-contained β€’ Easy to move/reuse components β€’ Parent doesn't need to know details </pre>

The Fragment Contract πŸ“‹

A fragment is a named piece of a GraphQL query that specifies data requirements for a single component. Think of it as a contract:

"If you give me a User object, I promise to only read these specific fields from it."

Basic Fragment Syntax

import { graphql } from 'react-relay';

const UserProfile = ({ user }) => (
  <div>
    <img src={user.avatar} alt={user.name} />
    <h2>{user.name}</h2>
    <p>{user.email}</p>
  </div>
);

export default createFragmentContainer(UserProfile, {
  user: graphql`
    fragment UserProfile_user on User {
      name
      email
      avatar
    }
  `
});

Breaking this down:

  • fragment UserProfile_user - The fragment name (convention: ComponentName_propName)
  • on User - The GraphQL type this fragment applies to
  • { name, email, avatar } - The exact fields this component needs

πŸ’‘ Naming Convention Tip: Always use ComponentName_propName format. This makes it clear which component owns which fragment and prevents naming collisions.

Colocation: Data Next to Code πŸ”—

The most powerful aspect of fragments is colocationβ€”keeping data requirements right next to the code that uses them. This creates several advantages:

1. Self-Documenting Components

When you look at a component, you immediately see what data it needs:

// Everything you need to know is right here!
const CommentCard = ({ comment }) => (
  <div className="comment">
    <span>{comment.author.name}</span>
    <p>{comment.text}</p>
    <time>{comment.createdAt}</time>
  </div>
);

export default createFragmentContainer(CommentCard, {
  comment: graphql`
    fragment CommentCard_comment on Comment {
      text
      createdAt
      author {
        name
      }
    }
  `
});

2. Safe Refactoring

Need to display the author's avatar? Just add it to the fragment:

fragment CommentCard_comment on Comment {
  text
  createdAt
  author {
    name
    avatar  # ← Add this
  }
}

Relay automatically updates all queries that use this fragment. No hunting through parent components!

3. Component Reusability

You can drop a component anywhere in your app. As long as the parent includes the fragment, it works:

// Works in UserPage
query UserPageQuery {
  user(id: $userId) {
    ...CommentCard_comment
  }
}

// Also works in PostPage
query PostPageQuery {
  post(id: $postId) {
    comments {
      ...CommentCard_comment
    }
  }
}

// Also works in NotificationFeed
query NotificationsQuery {
  notifications {
    comment {
      ...CommentCard_comment
    }
  }
}

Composing Fragments 🧩

Fragments compose just like React components compose. A parent component includes child fragments:

const PostCard = ({ post }) => (
  <article>
    <h2>{post.title}</h2>
    <AuthorBadge author={post.author} />
    <p>{post.excerpt}</p>
    <CommentList comments={post.comments} />
  </article>
);

export default createFragmentContainer(PostCard, {
  post: graphql`
    fragment PostCard_post on Post {
      title
      excerpt
      author {
        ...AuthorBadge_author
      }
      comments {
        ...CommentList_comments
      }
    }
  `
});

Notice:

  • PostCard doesn't know what AuthorBadge needsβ€”it just spreads the fragment
  • PostCard doesn't know what CommentList needsβ€”it just spreads the fragment
  • Each component is encapsulated and independent

<pre> FRAGMENT COMPOSITION TREE

    PostCard_post
         β”‚
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚                β”‚
 β–Ό                β–Ό

AuthorBadge_author CommentList_comments β”‚ β”‚ β–Ό β–Ό (name, avatar) β”Œβ”€β”€β”΄β”€β”€β” β”‚ β”‚ β–Ό β–Ό CommentCard_comment β”‚ β–Ό (text, author, time) </pre>

Example 1: Building a User Dashboard πŸ‘€

Let's build a user dashboard using fragments. We'll start with individual components and compose them:

Step 1: The Avatar Component

const Avatar = ({ user }) => (
  <img 
    src={user.profilePicture} 
    alt={user.name}
    className="avatar"
  />
);

export default createFragmentContainer(Avatar, {
  user: graphql`
    fragment Avatar_user on User {
      name
      profilePicture
    }
  `
});

Step 2: The Stats Component

const UserStats = ({ user }) => (
  <div className="stats">
    <div>
      <strong>{user.followerCount}</strong>
      <span>Followers</span>
    </div>
    <div>
      <strong>{user.followingCount}</strong>
      <span>Following</span>
    </div>
    <div>
      <strong>{user.postCount}</strong>
      <span>Posts</span>
    </div>
  </div>
);

export default createFragmentContainer(UserStats, {
  user: graphql`
    fragment UserStats_user on User {
      followerCount
      followingCount
      postCount
    }
  `
});

Step 3: The Dashboard (Composing Everything)

const UserDashboard = ({ user }) => (
  <div className="dashboard">
    <Avatar user={user} />
    <h1>{user.displayName}</h1>
    <p>{user.bio}</p>
    <UserStats user={user} />
  </div>
);

export default createFragmentContainer(UserDashboard, {
  user: graphql`
    fragment UserDashboard_user on User {
      displayName
      bio
      ...Avatar_user
      ...UserStats_user
    }
  `
});

What the final query looks like:

query DashboardPageQuery($userId: ID!) {
  user(id: $userId) {
    ...UserDashboard_user
  }
}

Relay automatically expands this to:

query DashboardPageQuery($userId: ID!) {
  user(id: $userId) {
    displayName
    bio
    name              # from Avatar_user
    profilePicture    # from Avatar_user
    followerCount     # from UserStats_user
    followingCount    # from UserStats_user
    postCount         # from UserStats_user
  }
}

πŸ”§ Try this: Add a new component UserBadges that shows achievement badges. You only need to:

  1. Create the component with its fragment
  2. Add ...UserBadges_user to UserDashboard_user
  3. Render <UserBadges user={user} /> in the JSX

No changes to the page query needed!

Example 2: Nested Fragments with Lists πŸ“

Let's build a blog feed that demonstrates fragments with collections:

The Author Badge (Reusable)

const AuthorBadge = ({ author }) => (
  <div className="author">
    <img src={author.avatar} alt="" />
    <span>{author.name}</span>
    {author.isVerified && <VerifiedIcon />}
  </div>
);

export default createFragmentContainer(AuthorBadge, {
  author: graphql`
    fragment AuthorBadge_author on User {
      name
      avatar
      isVerified
    }
  `
});

The Post Summary

const PostSummary = ({ post }) => (
  <article>
    <h3>{post.title}</h3>
    <AuthorBadge author={post.author} />
    <p>{post.excerpt}</p>
    <div className="meta">
      <span>{post.likeCount} likes</span>
      <span>{post.commentCount} comments</span>
      <time>{post.publishedAt}</time>
    </div>
  </article>
);

export default createFragmentContainer(PostSummary, {
  post: graphql`
    fragment PostSummary_post on Post {
      title
      excerpt
      likeCount
      commentCount
      publishedAt
      author {
        ...AuthorBadge_author
      }
    }
  `
});

The Feed (List of Posts)

const FeedList = ({ posts }) => (
  <div className="feed">
    {posts.map(post => (
      <PostSummary key={post.id} post={post} />
    ))}
  </div>
);

export default createFragmentContainer(FeedList, {
  posts: graphql`
    fragment FeedList_posts on Post @relay(plural: true) {
      id
      ...PostSummary_post
    }
  `
});

πŸ’‘ Notice @relay(plural: true): This directive tells Relay that posts is an array, not a single object. Always use this for list fragments.

The Page Query

query FeedPageQuery {
  posts(first: 10) {
    edges {
      node {
        ...FeedList_posts
      }
    }
  }
}

<div style="border: 2px solid #4fd1c5; border-radius: 8px; padding: 16px; margin: 16px 0; background: rgba(79, 209, 197, 0.1);"> <h4>🧠 Memory Device: The Matryoshka Doll Principle</h4> <p>Think of fragments like Russian nesting dolls (matryoshkas). Each doll (component) has its own complete shape (fragment), but they fit inside each other. You don't need to know what's inside the inner dollsβ€”you just know they fit.</p> <pre> β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FeedList_posts β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ PostSummary_post β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚AuthorBadge β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ _author β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ </pre> </div>

Example 3: Conditional Fragments πŸŽ›οΈ

Sometimes you need different data based on the GraphQL type. Use inline fragments for polymorphism:

const FeedItem = ({ item }) => {
  switch (item.__typename) {
    case 'Post':
      return <PostCard post={item} />;
    case 'Photo':
      return <PhotoCard photo={item} />;
    case 'Video':
      return <VideoCard video={item} />;
    default:
      return null;
  }
};

export default createFragmentContainer(FeedItem, {
  item: graphql`
    fragment FeedItem_item on FeedItemInterface {
      __typename
      
      ... on Post {
        ...PostCard_post
      }
      
      ... on Photo {
        ...PhotoCard_photo
      }
      
      ... on Video {
        ...VideoCard_video
      }
    }
  `
});

Key points:

  • __typename is automatically availableβ€”it tells you the concrete type
  • ... on Type is an inline fragment for a specific type
  • Each type can have completely different fields

<pre> POLYMORPHIC FEED STRUCTURE

FeedItemInterface
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”
β”‚      β”‚      β”‚
β–Ό      β–Ό      β–Ό

Post Photo Video β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό (title) (url) (duration) </pre>

Example 4: Arguments in Fragments 🎚️

Fragments can accept arguments, but they must be defined in the parent query:

const UserProfile = ({ user }) => (
  <div>
    <h2>{user.name}</h2>
    <p>{user.bio}</p>
    <img src={user.profilePicture} alt="Profile" />
  </div>
);

export default createFragmentContainer(UserProfile, {
  user: graphql`
    fragment UserProfile_user on User
    @argumentDefinitions(
      size: { type: "Int", defaultValue: 200 }
    ) {
      name
      bio
      profilePicture(size: $size)
    }
  `
});

Using it in a query:

query ProfilePageQuery($userId: ID!) {
  user(id: $userId) {
    ...UserProfile_user @arguments(size: 400)
  }
}

πŸ’‘ When to use arguments:

  • Image sizes (thumbnails vs. full-size)
  • Pagination limits (first: 10 vs. first: 50)
  • Date formatting
  • Sorting options

⚠️ Keep it simple: Most fragments don't need arguments. Use them sparingly for truly reusable components that need flexibility.

Common Mistakes and How to Avoid Them ⚠️

Mistake 1: Thinking Like REST

❌ Wrong:

// Fetching everything in the container
const PageContainer = () => {
  const data = useQuery(graphql`
    query PageQuery {
      user { id, name, email, avatar, bio, ... }
      posts { id, title, content, author, ... }
    }
  `);
  
  return (
    <>
      <UserProfile user={data.user} />
      <PostList posts={data.posts} />
    </>
  );
};

βœ… Right:

// Components declare their needs
const PageContainer = () => {
  const data = useQuery(graphql`
    query PageQuery {
      user { ...UserProfile_user }
      posts { ...PostList_posts }
    }
  `);
  
  return (
    <>
      <UserProfile user={data.user} />
      <PostList posts={data.posts} />
    </>
  );
};

Mistake 2: Not Spreading Child Fragments

❌ Wrong:

fragment PostCard_post on Post {
  title
  author {
    name  # ← Directly querying child fields
    avatar
  }
}

βœ… Right:

fragment PostCard_post on Post {
  title
  author {
    ...AuthorBadge_author  # ← Spreading child fragment
  }
}

Why it matters: If AuthorBadge needs more fields later (like isVerified), the wrong approach requires updating PostCard_post. The right approach handles it automatically.

Mistake 3: Over-Fetching in Fragments

❌ Wrong:

fragment Avatar_user on User {
  id
  name
  email          # ← Not used in component
  bio            # ← Not used in component
  followerCount  # ← Not used in component
  avatar
}

βœ… Right:

fragment Avatar_user on User {
  name    # Used in alt text
  avatar  # Used in src
}

Principle: Only request fields you actually render or use in logic. Every field has a cost.

Mistake 4: Missing @relay(plural: true)

❌ Wrong:

fragment CommentList_comments on Comment {
  id
  text
}

This will cause runtime errors when you try to map over comments.

βœ… Right:

fragment CommentList_comments on Comment @relay(plural: true) {
  id
  text
}

Mistake 5: Inconsistent Naming

❌ Wrong:

// Random names
fragment userFrag on User { ... }
fragment Post_Fragment on Post { ... }
fragment commentData_stuff on Comment { ... }

βœ… Right:

// Consistent ComponentName_propName pattern
fragment UserProfile_user on User { ... }
fragment PostCard_post on Post { ... }
fragment CommentList_comments on Comment { ... }

<table> <tr><th>Anti-Pattern</th><th>Consequence</th><th>Fix</th></tr> <tr><td>Querying in container</td><td>Tight coupling</td><td>Use fragments</td></tr> <tr><td>Not spreading children</td><td>Brittle updates</td><td>Always spread</td></tr> <tr><td>Fetching unused fields</td><td>Slow queries</td><td>Minimal fragments</td></tr> <tr><td>Missing plural directive</td><td>Runtime errors</td><td>Add @relay(plural: true)</td></tr> <tr><td>Inconsistent names</td><td>Confusion</td><td>ComponentName_propName</td></tr> </table>

The Mental Model Shift: Before and After 🧠

Before: Query-Centric Thinking

<pre> "I need to build a profile page. What data does this page need?" β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Write big query with all data β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Pass data down as props β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Components are prop-dependent β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⚠️ Result: Tightly coupled system </pre>

After: Fragment-Centric Thinking

<pre> "I need to build a profile page. What components does this page have?" β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Build each component with β”‚ β”‚ its own fragment β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Compose fragments in parent β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Write minimal page query that β”‚ β”‚ just spreads root fragment β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

βœ… Result: Loosely coupled, composable system </pre>

Key Principles to Remember 🎯

  1. Fragments over Queries: Think about component data needs, not page data needs
  2. Colocation: Keep data requirements next to the code that uses them
  3. Composition: Parent components spread child fragments, don't duplicate them
  4. Encapsulation: Components shouldn't know about their children's data needs
  5. Minimal Fetching: Only request fields you actually use

πŸ€” Did you know? Facebook's codebase has tens of thousands of fragments. The largest queries are automatically composed from hundreds of nested fragments. This system scales because no single developer needs to understand the entire data graphβ€”just their component's fragment.

<div style="border: 2px solid #4fd1c5; border-radius: 8px; padding: 16px; margin: 16px 0; background: rgba(79, 209, 197, 0.1);"> <h4>πŸ“‹ Quick Reference Card: Query vs. Fragment Mindset</h4> <table> <tr> <th>Query Mindset ❌</th> <th>Fragment Mindset βœ…</th> </tr> <tr> <td>"What does the page need?"</td> <td>"What does this component need?"</td> </tr> <tr> <td>Container knows all child data</td> <td>Each component declares its own data</td> </tr> <tr> <td>Props passed down from query</td> <td>Fragments composed up to query</td> </tr> <tr> <td>Change component β†’ update parent</td> <td>Change component β†’ fragment updates</td> </tr> <tr> <td>Hard to move/reuse components</td> <td>Easy to move/reuse components</td> </tr> <tr> <td>Tight coupling</td> <td>Loose coupling</td> </tr> </table>

<h4>Fragment Syntax Cheatsheet</h4> <table> <tr><td><strong>Basic fragment</strong></td><td><code>fragment Name_prop on Type </code></td></tr> <tr><td><strong>Spread fragment</strong></td><td><code>...ComponentName_prop</code></td></tr> <tr><td><strong>Plural/array</strong></td><td><code>@relay(plural: true)</code></td></tr> <tr><td><strong>With arguments</strong></td><td><code>@argumentDefinitions(arg: {type: "Type"})</code></td></tr> <tr><td><strong>Inline fragment</strong></td><td><code>... on ConcreteType </code></td></tr> <tr><td><strong>Naming convention</strong></td><td><code>ComponentName_propName</code></td></tr> </table> </div>

Practical Exercise: Refactor a Query-Based Component πŸ”§

Let's practice the mental shift. Here's a traditional query-based component:

Before (Query-Centric):

const BlogPost = ({ postId }) => {
  const data = useQuery(graphql`
    query BlogPostQuery($postId: ID!) {
      post(id: $postId) {
        id
        title
        content
        publishedAt
        author {
          id
          name
          avatar
          bio
        }
        comments {
          id
          text
          createdAt
          author {
            id
            name
            avatar
          }
        }
      }
    }
  `);
  
  return (
    <article>
      <h1>{data.post.title}</h1>
      <div className="author">
        <img src={data.post.author.avatar} />
        <span>{data.post.author.name}</span>
        <p>{data.post.author.bio}</p>
      </div>
      <div>{data.post.content}</div>
      <div className="comments">
        {data.post.comments.map(comment => (
          <div key={comment.id}>
            <img src={comment.author.avatar} />
            <span>{comment.author.name}</span>
            <p>{comment.text}</p>
            <time>{comment.createdAt}</time>
          </div>
        ))}
      </div>
    </article>
  );
};

After (Fragment-Centric):

// Step 1: Extract AuthorCard component
const AuthorCard = ({ author }) => (
  <div className="author">
    <img src={author.avatar} alt="" />
    <span>{author.name}</span>
    <p>{author.bio}</p>
  </div>
);

export default createFragmentContainer(AuthorCard, {
  author: graphql`
    fragment AuthorCard_author on User {
      name
      avatar
      bio
    }
  `
});

// Step 2: Extract Comment component
const Comment = ({ comment }) => (
  <div className="comment">
    <img src={comment.author.avatar} alt="" />
    <span>{comment.author.name}</span>
    <p>{comment.text}</p>
    <time>{comment.createdAt}</time>
  </div>
);

export default createFragmentContainer(Comment, {
  comment: graphql`
    fragment Comment_comment on Comment {
      text
      createdAt
      author {
        name
        avatar
      }
    }
  `
});

// Step 3: Extract CommentList component
const CommentList = ({ comments }) => (
  <div className="comments">
    {comments.map(comment => (
      <Comment key={comment.id} comment={comment} />
    ))}
  </div>
);

export default createFragmentContainer(CommentList, {
  comments: graphql`
    fragment CommentList_comments on Comment @relay(plural: true) {
      id
      ...Comment_comment
    }
  `
});

// Step 4: Main BlogPost component
const BlogPost = ({ post }) => (
  <article>
    <h1>{post.title}</h1>
    <AuthorCard author={post.author} />
    <div>{post.content}</div>
    <CommentList comments={post.comments} />
  </article>
);

export default createFragmentContainer(BlogPost, {
  post: graphql`
    fragment BlogPost_post on Post {
      title
      content
      author {
        ...AuthorCard_author
      }
      comments {
        ...CommentList_comments
      }
    }
  `
});

// Step 5: Minimal page query
const BlogPostPage = ({ postId }) => {
  const data = useQuery(graphql`
    query BlogPostPageQuery($postId: ID!) {
      post(id: $postId) {
        ...BlogPost_post
      }
    }
  `);
  
  return <BlogPost post={data.post} />;
};

Benefits of the refactor:

  • βœ… AuthorCard can be reused anywhere
  • βœ… Comment can be reused in notifications, activity feeds, etc.
  • βœ… Changing Comment doesn't require touching BlogPost
  • βœ… Each component is self-documenting
  • βœ… Easy to test components in isolation

Key Takeaways πŸŽ“

  1. Stop writing big queries at the top β€” Start with fragments in leaf components

  2. Fragments are contracts β€” "Give me this type, I'll only read these fields"

  3. Colocation is powerful β€” Data requirements live with the code that uses them

  4. Composition over duplication β€” Spread child fragments, don't duplicate their fields

  5. Think bottom-up β€” Build components with fragments first, compose into queries last

  6. Encapsulation enables reuse β€” Components that declare their own data are easier to move around

  7. Follow naming conventions β€” ComponentName_propName keeps things organized

  8. Use directives appropriately β€” @relay(plural: true) for arrays, @argumentDefinitions for flexibility

  9. Keep fragments minimal β€” Only request what you render

  10. Trust the system β€” Relay handles query composition, deduplication, and optimization for you

πŸ“š Further Study


Next Steps: Now that you understand fragments, you're ready to learn about Relay's data-driven rendering system and how fragments integrate with React Suspense. The fragment-first mindset you've developed here is the foundation for everything else in Relay!