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

Fragment Colocation Principle

Learn why components declare their own data requirements alongside their implementation

Fragment Colocation Principle

Master React Relay's fragment colocation with free flashcards and spaced repetition practice. This lesson covers data dependency colocating with components, fragment composition patterns, and type-safe data fetchingβ€”essential concepts for building scalable GraphQL applications with Relay.

Welcome to Fragment Colocation πŸ’»

In traditional React applications, data requirements are often scattered across multiple filesβ€”queries defined in one location, components consuming data in another, and business logic somewhere else entirely. This separation creates maintenance nightmares: when you need to add a field to your UI, you hunt through query files, worry about breaking other components, and hope you've updated everything correctly.

Relay's fragment colocation principle solves this by keeping data requirements right next to the components that use them. Each component declares exactly what data it needs using fragments, and Relay automatically composes these fragments into efficient queries. This isn't just about convenienceβ€”it's a fundamental shift in how we architect data-driven applications.

Think of it like a restaurant kitchen where each chef keeps their own ingredients and tools at their station (colocated) versus a chaotic kitchen where everyone shares one pantry and constantly gets in each other's way. Colocation creates clear boundaries, reduces conflicts, and makes the system easier to understand and modify.

Core Concepts: Understanding Colocation 🧩

What is Fragment Colocation?

Fragment colocation means defining your data requirements in the same file as the component that consumes that data. Instead of writing queries in a separate "queries" folder, each component declares its data needs using GraphQL fragments.

Here's the anatomy of a colocated fragment:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         COMPONENT FILE                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                             β”‚
β”‚  import { graphql, useFragment } from...   β”‚
β”‚                                             β”‚
β”‚  function UserProfile({ userRef }) {       β”‚
β”‚    const data = useFragment(               β”‚
β”‚      USER_FRAGMENT,                        β”‚
β”‚      userRef                               β”‚
β”‚    );                                      β”‚
β”‚                                             β”‚
β”‚    return 
{data.name}
; β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ const USER_FRAGMENT = graphql` β”‚ β”‚ fragment UserProfile_user on User { β”‚ β”‚ name β”‚ β”‚ email β”‚ β”‚ avatar β”‚ β”‚ } β”‚ β”‚ `; β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↑ ↑ β”‚ β”‚ Component UI Fragment Definition (data consumer) (data requirements) ↓ COLOCATED in same file!

Why Colocation Matters 🎯

1. Local Reasoning

When you open a component file, you immediately see:

  • What data it needs (the fragment)
  • How it renders that data (the JSX)
  • What types are involved (TypeScript types generated from the fragment)

No jumping between files, no guessing about dependencies.

2. Safe Refactoring

Want to add a new field? Just:

  1. Add it to the fragment
  2. Use it in the component
  3. Done.

Relay's compiler ensures the field exists and generates proper TypeScript types. No other components are affected because they have their own fragments.

3. Automatic Composition

Relay composes child fragments into parent queries automatically:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PARENT COMPONENT                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ Query: FeedQuery               β”‚         β”‚
β”‚  β”‚   user {                       β”‚         β”‚
β”‚  β”‚     ...UserHeader_user         β”‚ ◄───┐   β”‚
β”‚  β”‚     posts {                    β”‚     β”‚   β”‚
β”‚  β”‚       ...PostList_posts        β”‚ ◄─┐ β”‚   β”‚
β”‚  β”‚     }                          β”‚   β”‚ β”‚   β”‚
β”‚  β”‚   }                            β”‚   β”‚ β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚ β”‚   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”Όβ”€β”€β”€β”˜
                                        β”‚ β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
            β”‚                             β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CHILD COMPONENT        β”‚   β”‚  CHILD COMPONENT   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Fragment:         β”‚  β”‚   β”‚  β”‚ Fragment:    β”‚  β”‚
β”‚  β”‚ UserHeader_user   β”‚  β”‚   β”‚  β”‚ PostList_    β”‚  β”‚
β”‚  β”‚   name            β”‚  β”‚   β”‚  β”‚ posts        β”‚  β”‚
β”‚  β”‚   avatar          β”‚  β”‚   β”‚  β”‚   title      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚  β”‚   content    β”‚  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  Parent spreads (...) child fragments
  β†’ Relay combines them into one query
  β†’ Each component gets exactly its data

The Fragment Spreading Pattern πŸ“€

Parent components "spread" child fragments using the ...FragmentName syntax:

// Parent query spreads child fragments
const FEED_QUERY = graphql`
  query FeedQuery {
    viewer {
      ...UserHeader_user
      posts {
        edges {
          node {
            ...PostCard_post
          }
        }
      }
    }
  }
`;

This pattern creates a data dependency graph that mirrors your component hierarchy. The parent doesn't need to know what fields UserHeader needsβ€”it just spreads the fragment and passes the reference down.

Fragment References: The Key to Type Safety πŸ”‘

When you spread a fragment, Relay creates an opaque fragment referenceβ€”a special value that can only be read by the fragment that created it:

Concept What It Looks Like Purpose
Fragment Spread ...PostCard_post Include child fragment in query
Fragment Reference postRef: PostCard_post$key Opaque pointer to fragment data
useFragment Hook useFragment(FRAGMENT, ref) Read data from fragment reference

The reference is opaque intentionallyβ€”the parent can't peek at the data, ensuring that only the component that defined the fragment can access it. This prevents accidental coupling and makes refactoring safe.

Deep Dive: The Colocation Workflow πŸ”„

Step-by-Step: Building a Colocated Component

Let's build a UserBadge component that displays a user's name and follower count:

Step 1: Define the component structure

import { graphql, useFragment } from 'react-relay';
import type { UserBadge_user$key } from './__generated__/UserBadge_user.graphql';

interface UserBadgeProps {
  userRef: UserBadge_user$key;  // Fragment reference type
}

function UserBadge({ userRef }: UserBadgeProps) {
  // We'll read data here
  return <div>Badge content</div>;
}

export default UserBadge;

Step 2: Define the fragment

const USER_BADGE_FRAGMENT = graphql`
  fragment UserBadge_user on User {
    name
    followerCount
    isVerified
  }
`;

Step 3: Read the data

function UserBadge({ userRef }: UserBadgeProps) {
  const data = useFragment(USER_BADGE_FRAGMENT, userRef);
  
  return (
    <div className="badge">
      <span>{data.name}</span>
      {data.isVerified && <VerifiedIcon />}
      <span>{data.followerCount} followers</span>
    </div>
  );
}

Step 4: Use it in a parent

// Parent component
function UserProfile() {
  const data = useLazyLoadQuery(
    graphql`
      query UserProfileQuery($userId: ID!) {
        user(id: $userId) {
          ...UserBadge_user  # Spread the fragment
          bio
        }
      }
    `,
    { userId: '123' }
  );
  
  return (
    <div>
      <UserBadge userRef={data.user} />  {/* Pass reference */}
      <p>{data.bio}</p>
    </div>
  );
}

Fragment Naming Conventions πŸ“›

Relay enforces a naming convention: ComponentName_propName

Fragment Name Component Prop Name Generated Type
UserBadge_user UserBadge user UserBadge_user$key
PostCard_post PostCard post PostCard_post$key
CommentList_comments CommentList comments CommentList_comments$key

This convention makes it easy to:

  • Identify which component owns a fragment
  • Know what prop should receive the data
  • Find fragments in your codebase (search for ComponentName_)

Multiple Fragments in One Component 🎭

A component can use multiple fragments:

function PostDetail({ postRef, authorRef }) {
  const post = useFragment(
    graphql`
      fragment PostDetail_post on Post {
        title
        content
        publishedAt
      }
    `,
    postRef
  );
  
  const author = useFragment(
    graphql`
      fragment PostDetail_author on User {
        name
        avatar
      }
    `,
    authorRef
  );
  
  return (
    <article>
      <h1>{post.title}</h1>
      <AuthorByline author={author} />
      <div>{post.content}</div>
    </article>
  );
}

Each fragment follows the naming convention and gets its own reference.

Examples: Colocation in Action πŸš€

Example 1: Simple Profile Card

Let's build a profile card with proper colocation:

// ProfileCard.tsx
import { graphql, useFragment } from 'react-relay';
import type { ProfileCard_user$key } from './__generated__/ProfileCard_user.graphql';

interface Props {
  userRef: ProfileCard_user$key;
}

function ProfileCard({ userRef }: Props) {
  const user = useFragment(
    graphql`
      fragment ProfileCard_user on User {
        id
        name
        email
        profilePicture {
          url
        }
        memberSince
      }
    `,
    userRef
  );
  
  const memberDate = new Date(user.memberSince);
  
  return (
    <div className="profile-card">
      <img src={user.profilePicture.url} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>Member since {memberDate.getFullYear()}</p>
    </div>
  );
}

export default ProfileCard;

What's happening here:

  1. TypeScript import: The generated type ProfileCard_user$key ensures type safety
  2. Fragment definition: Declares exactly the fields needed for this UI
  3. useFragment hook: Reads data from the reference
  4. Business logic: Date formatting happens in the component that needs it
  5. Self-contained: Everything about rendering this card is in one file

Parent usage:

// Dashboard.tsx
function Dashboard() {
  const data = useLazyLoadQuery(
    graphql`
      query DashboardQuery {
        viewer {
          ...ProfileCard_user  # Just spread it!
        }
      }
    `,
    {}
  );
  
  return <ProfileCard userRef={data.viewer} />;
}

The parent doesn't know (or care) what fields ProfileCard needs. It just spreads the fragment and passes the reference.

Example 2: Nested Composition

Let's build a blog post with nested components:

// PostHeader.tsx
function PostHeader({ postRef }) {
  const post = useFragment(
    graphql`
      fragment PostHeader_post on Post {
        title
        publishedAt
        readTime
      }
    `,
    postRef
  );
  
  return (
    <header>
      <h1>{post.title}</h1>
      <time>{post.publishedAt}</time>
      <span>{post.readTime} min read</span>
    </header>
  );
}

// PostBody.tsx
function PostBody({ postRef }) {
  const post = useFragment(
    graphql`
      fragment PostBody_post on Post {
        content
        images {
          url
          caption
        }
      }
    `,
    postRef
  );
  
  return (
    <article>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      {post.images.map(img => (
        <figure key={img.url}>
          <img src={img.url} alt={img.caption} />
          <figcaption>{img.caption}</figcaption>
        </figure>
      ))}
    </article>
  );
}

// PostDetail.tsx (parent)
function PostDetail({ postRef }) {
  const post = useFragment(
    graphql`
      fragment PostDetail_post on Post {
        ...PostHeader_post
        ...PostBody_post
        author {
          name
        }
      }
    `,
    postRef
  );
  
  return (
    <div>
      <PostHeader postRef={post} />
      <PostBody postRef={post} />
      <footer>By {post.author.name}</footer>
    </div>
  );
}

Composition flow:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PostDetail Fragment                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ ...PostHeader_post            β”‚  β”‚  ──→  PostHeader
β”‚  β”‚   title                       β”‚  β”‚        gets only:
β”‚  β”‚   publishedAt                 β”‚  β”‚        title, publishedAt
β”‚  β”‚   readTime                    β”‚  β”‚        readTime
β”‚  β”‚                               β”‚  β”‚
β”‚  β”‚ ...PostBody_post              β”‚  β”‚  ──→  PostBody
β”‚  β”‚   content                     β”‚  β”‚        gets only:
β”‚  β”‚   images { url, caption }     β”‚  β”‚        content, images
β”‚  β”‚                               β”‚  β”‚
β”‚  β”‚ author { name }               β”‚  β”‚  ──→  PostDetail
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚        keeps:
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        author.name

Each component sees ONLY its fragment data!

Notice that PostDetail spreads both child fragments AND has its own fields (author.name). This is perfectly validβ€”parent components can mix spreads with their own data needs.

Example 3: Lists and Edges

Handling lists requires spreading fragments over arrays:

// CommentItem.tsx
function CommentItem({ commentRef }) {
  const comment = useFragment(
    graphql`
      fragment CommentItem_comment on Comment {
        id
        text
        author {
          name
          avatar
        }
        createdAt
      }
    `,
    commentRef
  );
  
  return (
    <div className="comment">
      <img src={comment.author.avatar} alt="" />
      <div>
        <strong>{comment.author.name}</strong>
        <p>{comment.text}</p>
        <time>{comment.createdAt}</time>
      </div>
    </div>
  );
}

// CommentList.tsx
function CommentList({ commentsRef }) {
  const data = useFragment(
    graphql`
      fragment CommentList_comments on Post {
        comments(first: 10) {
          edges {
            node {
              id
              ...CommentItem_comment
            }
          }
        }
      }
    `,
    commentsRef
  );
  
  return (
    <div className="comments">
      <h3>Comments</h3>
      {data.comments.edges.map(edge => (
        <CommentItem 
          key={edge.node.id} 
          commentRef={edge.node} 
        />
      ))}
    </div>
  );
}

Key pattern for lists:

  1. Parent fragment fetches the connection (edges/nodes)
  2. Parent spreads child fragment on node
  3. Parent maps over edges and passes each node reference to child
  4. Child reads its fragment data from the reference

This pattern works for any Relay connection (pagination-compatible lists).

Example 4: Conditional Fragments

Fragments can include conditional logic:

function UserProfile({ userRef, showPrivateInfo }) {
  const user = useFragment(
    graphql`
      fragment UserProfile_user on User {
        id
        name
        avatar
        
        # Conditional field with directive
        email @include(if: $showPrivateInfo)
        phone @include(if: $showPrivateInfo)
        
        # Inline fragments for different types
        ... on PremiumUser {
          subscriptionTier
          memberSince
        }
        
        ... on FreeUser {
          upgradePromptDismissed
        }
      }
    `,
    userRef
  );
  
  return (
    <div>
      <h1>{user.name}</h1>
      <img src={user.avatar} alt="" />
      
      {user.email && <p>Email: {user.email}</p>}
      
      {user.subscriptionTier && (
        <Badge tier={user.subscriptionTier} />
      )}
      
      {user.upgradePromptDismissed === false && (
        <UpgradePrompt />
      )}
    </div>
  );
}

Conditional patterns:

  • @include(if: $variable): Include field only if variable is true
  • @skip(if: $variable): Skip field if variable is true
  • Inline fragments (... on Type): Include fields for specific types
  • Check for undefined: In component, check if conditional fields exist

Common Mistakes ⚠️

Mistake 1: Accessing Data Without useFragment

❌ Wrong:

function UserBadge({ userRef }) {
  // Trying to access userRef directly
  return <div>{userRef.name}</div>;  // ERROR!
}

βœ… Correct:

function UserBadge({ userRef }) {
  // Always use useFragment to read data
  const user = useFragment(USER_BADGE_FRAGMENT, userRef);
  return <div>{user.name}</div>;
}

Why: Fragment references are opaqueβ€”you can't access their properties directly. This enforces the colocation contract.

Mistake 2: Wrong Fragment Naming

❌ Wrong:

// Fragment name doesn't match component
const USER_FRAGMENT = graphql`
  fragment userInfo on User {  # Should be UserBadge_user
    name
  }
`;

βœ… Correct:

const USER_FRAGMENT = graphql`
  fragment UserBadge_user on User {
    name
  }
`;

Why: Relay requires the ComponentName_propName convention for code generation and type safety.

Mistake 3: Forgetting to Spread Fragment in Parent

❌ Wrong:

// Parent query
const QUERY = graphql`
  query MyQuery {
    user {
      name  # Fetching fields directly
      email
    }
  }
`;

// Then passing to child
<UserBadge userRef={data.user} />  // ERROR: No fragment reference!

βœ… Correct:

const QUERY = graphql`
  query MyQuery {
    user {
      ...UserBadge_user  # Spread the fragment
    }
  }
`;

<UserBadge userRef={data.user} />  // Now data.user is a proper reference

Why: Fragment references are only created when you spread a fragment. Fetching fields directly doesn't create the reference.

Mistake 4: Over-fetching in Parent

❌ Wrong:

// Parent fetches everything "just in case"
const QUERY = graphql`
  query MyQuery {
    user {
      id
      name
      email
      phone
      address
      preferences
      ...UserBadge_user
    }
  }
`;

βœ… Correct:

// Parent fetches only what it needs directly
const QUERY = graphql`
  query MyQuery {
    user {
      id  # Only if parent uses it
      ...UserBadge_user  # Child declares its needs
    }
  }
`;

Why: Colocation means each component declares its own needs. The parent shouldn't fetch fields it doesn't useβ€”let children specify their requirements.

Mistake 5: Treating References as Regular Objects

❌ Wrong:

function Parent() {
  const data = useLazyLoadQuery(QUERY, {});
  
  // Trying to transform before passing
  const transformedUser = {
    ...data.user,
    displayName: data.user.name.toUpperCase()
  };
  
  return <UserBadge userRef={transformedUser} />;  // ERROR!
}

βœ… Correct:

function Parent() {
  const data = useLazyLoadQuery(QUERY, {});
  
  // Pass reference as-is
  return <UserBadge userRef={data.user} />;
}

// If child needs transformation, do it after useFragment
function UserBadge({ userRef }) {
  const user = useFragment(FRAGMENT, userRef);
  const displayName = user.name.toUpperCase();
  return <div>{displayName}</div>;
}

Why: Fragment references are opaque tokens. Don't spread them, transform them, or treat them as regular objects. Pass them directly to useFragment.

Mistake 6: Circular Fragment Dependencies

❌ Wrong:

// ComponentA spreads ComponentB's fragment
const FRAGMENT_A = graphql`
  fragment ComponentA_data on Node {
    ...ComponentB_data
  }
`;

// ComponentB spreads ComponentA's fragment
const FRAGMENT_B = graphql`
  fragment ComponentB_data on Node {
    ...ComponentA_data  # CIRCULAR!
  }
`;

βœ… Correct:

// Create a shared fragment for common data
const SHARED_FRAGMENT = graphql`
  fragment SharedData_node on Node {
    id
    commonField
  }
`;

const FRAGMENT_A = graphql`
  fragment ComponentA_data on Node {
    ...SharedData_node
    specificToA
  }
`;

const FRAGMENT_B = graphql`
  fragment ComponentB_data on Node {
    ...SharedData_node
    specificToB
  }
`;

Why: Circular dependencies break Relay's compiler. Extract shared needs into a separate fragment that both can spread.

Key Takeaways 🎯

πŸ’‘ The Colocation Contract

  • Components declare their data needs using fragments
  • Fragments live in the same file as the component
  • Parents spread child fragments without knowing the details
  • Relay composes everything into efficient queries

πŸ’‘ Benefits of Colocation

  • Local reasoning: Everything you need is in one place
  • Safe refactoring: Changes are localized to one component
  • Type safety: Generated types catch errors at compile time
  • Automatic optimization: Relay deduplicates and batches requests

πŸ’‘ The Fragment Reference Pattern

  • Spreading a fragment creates an opaque reference
  • References are typed with FragmentName$key
  • Only useFragment can read the reference
  • This enforces boundaries between components

πŸ’‘ Composition is Key

  • Fragments compose like React components
  • Use ...FragmentName to spread child fragments
  • Parent queries stitch everything together
  • Each component remains independent

πŸ’‘ Naming Matters

  • Follow ComponentName_propName convention strictly
  • Makes codebases searchable and predictable
  • Enables proper type generation
  • Signals ownership clearly

πŸ“‹ Quick Reference: Fragment Colocation Checklist

βœ… Do ❌ Don't
Define fragments in same file as component Put fragments in separate query files
Use ComponentName_propName naming Use arbitrary fragment names
Spread child fragments with ... Manually duplicate child fields
Pass fragment references as-is Transform or spread references
Use useFragment to read data Access reference properties directly
Let each component declare its needs Over-fetch in parent "just in case"

🧠 Mental Model: The Component Tree = Data Tree

Your React Tree          Your Fragment Tree
<App>                  AppQuery {
  |                      viewer {
<Feed>                   ...Feed_viewer
  |                        posts {
<Post>                     ...Post_post
  |                          comments {
<Comment>                    ...Comment_comment
                    

One-to-one correspondence! Data flows down just like props.

πŸ“š Further Study

Official Relay Documentation:


πŸŽ“ Next Steps:

You've mastered the fragment colocation principleβ€”the foundation of Relay's architecture. You now understand:

  • Why colocation matters for maintainability
  • How fragments compose to form queries
  • The role of fragment references in type safety
  • Common pitfalls and how to avoid them

Next in your journey: Learn about Fragment Containers and Composition Patterns to handle more complex scenarios like pagination, refetching, and optimistic updates.

Practice building components with colocated fragments, and notice how much easier it becomes to reason about your data layer. The initial learning curve pays off with dramatically improved developer experience! πŸš€