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

useFragment for Components

Reading fragment data passed from parent components via fragment references

useFragment for Components

Master the useFragment hook with free flashcards and spaced repetition practice. This lesson covers fragment colocation patterns, component data requirements, and type-safe fragment consumptionβ€”essential concepts for building scalable Relay applications.

Welcome to Fragment-Driven Components πŸ’»

In Relay, components declare their own data requirements through fragments. The useFragment hook is your primary tool for consuming these fragments, enabling a powerful pattern where each component is self-contained with its data needs. This approach creates maintainable, reusable components that can't break when parent queries change.

Think of useFragment as a contract enforcement mechanism: it takes a fragment reference (a pointer to data) and returns the actual data your component needs. This indirection is what makes Relay's data masking and component composition so powerful.

Core Concepts: Understanding useFragment

What is useFragment?

useFragment is a React hook that reads fragment data from the Relay store. It takes two arguments:

  1. Fragment definition (created with graphql tagged template literal)
  2. Fragment reference (passed from parent component as props)

Here's the fundamental pattern:

import { useFragment, graphql } from 'react-relay';

function UserCard({ userRef }) {
  const data = useFragment(
    graphql`
      fragment UserCard_user on User {
        name
        email
        avatarUrl
      }
    `,
    userRef  // ← Fragment reference from parent
  );

  return (
    

{data.name}

{data.email}

); }

The Fragment Reference Flow πŸ”„

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          PARENT COMPONENT (Query)               β”‚
β”‚                                                 β”‚
β”‚  useLazyLoadQuery(graphql`                     β”‚
β”‚    query AppQuery {                             β”‚
β”‚      user {                                     β”‚
β”‚        ...UserCard_user  ← Spreads fragment    β”‚
β”‚      }                                          β”‚
β”‚    }                                            β”‚
β”‚  `)                                             β”‚
β”‚                                                 β”‚
β”‚    ← Pass ref  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 ↓ (fragment reference)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          CHILD COMPONENT (Fragment)             β”‚
β”‚                                                 β”‚
β”‚  useFragment(                                   β”‚
β”‚    graphql`                                     β”‚
β”‚      fragment UserCard_user on User {          β”‚
β”‚        name                                     β”‚
β”‚        email                                    β”‚
β”‚      }                                          β”‚
β”‚    `,                                           β”‚
β”‚    userRef  ← Receives reference                β”‚
β”‚  )                                              β”‚
β”‚                                                 β”‚
β”‚  β†’ Returns actual data { name, email }         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Masking: The Key Benefit πŸ”’

Relay implements data masking to ensure components only access data they explicitly request. When you spread a fragment (...UserCard_user), the parent component receives an opaque reference, not the actual data.

Why this matters:

Without Data Masking With Data Masking (Relay)
❌ Child depends on parent's query βœ… Child declares own requirements
❌ Changes break multiple components βœ… Changes are isolated and safe
❌ Hard to track data dependencies βœ… Dependencies are explicit in code
❌ Prop drilling for all data βœ… Pass single fragment reference

πŸ’‘ Key Insight: The fragment reference is like a "ticket" – it proves the parent fetched the data, but only useFragment can redeem it for actual values.

Fragment Naming Conventions πŸ“

Relay enforces strict naming: <ComponentName>_<propName>

// Component: UserProfile.jsx
// Prop name: user
fragment UserProfile_user on User { ... }

// Component: CommentList.jsx  
// Prop name: comments
fragment CommentList_comments on Comment @relay(plural: true) { ... }

// Component: PostCard.jsx
// Prop name: post
fragment PostCard_post on Post { ... }

This convention:

  • Prevents name collisions across your codebase
  • Makes dependencies searchable (find all fragments used by a component)
  • Enables Relay compiler optimizations and better error messages

⚠️ Common Mistake: Don't use generic names like fragment_data or MyFragment – Relay compiler will reject them!

Working with Fragment Types 🎯

TypeScript Integration

Relay generates TypeScript types automatically when you run the compiler. Each fragment gets a type you can import:

import type { UserCard_user$key } from './__generated__/UserCard_user.graphql';

type Props = {
  userRef: UserCard_user$key;  // ← Generated fragment ref type
};

function UserCard({ userRef }: Props) {
  const data = useFragment(
    graphql`
      fragment UserCard_user on User {
        name
        email
      }
    `,
    userRef
  );
  
  // 'data' has type: { name: string; email: string }
  // Fully type-safe!
}

Fragment Key Types Pattern

Relay generates three types per fragment:

Type Suffix Purpose Usage
$key Fragment reference type Props that receive fragment refs
$data Actual data shape Return type from useFragment
$fragmentType Internal discriminator Used by Relay runtime (rarely needed)

Nullable Fragment References

Sometimes data might not exist. Handle with type unions:

import type { UserCard_user$key } from './__generated__/UserCard_user.graphql';

type Props = {
  userRef: UserCard_user$key | null;  // ← Can be null
};

function UserCard({ userRef }: Props) {
  const data = useFragment(
    graphql`
      fragment UserCard_user on User {
        name
        email
      }
    `,
    userRef  // ← useFragment handles null automatically
  );
  
  if (!data) {
    return <div>No user data available</div>;
  }
  
  return <div>{data.name}</div>;
}

πŸ’‘ Pro Tip: useFragment returns null when passed a null reference, making null-handling ergonomic.

Practical Examples

Example 1: Basic User Card Component

Let's build a complete example showing parent-child fragment composition:

UserCard.jsx (Child Component):

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

type Props = {
  userRef: UserCard_user$key;
};

export default function UserCard({ userRef }: Props) {
  const user = useFragment(
    graphql`
      fragment UserCard_user on User {
        id
        name
        email
        avatarUrl
        isVerified
      }
    `,
    userRef
  );

  return (
    
{user.name}

{user.name} {user.isVerified && βœ“}

{user.email}

); }

App.jsx (Parent Component):

import { useLazyLoadQuery, graphql } from 'react-relay';
import UserCard from './UserCard';
import type { AppQuery } from './__generated__/AppQuery.graphql';

export default function App() {
  const data = useLazyLoadQuery(
    graphql`
      query AppQuery($userId: ID!) {
        user(id: $userId) {
          ...UserCard_user  # ← Spread child's fragment
        }
      }
    `,
    { userId: '123' }
  );

  return (
    

User Profile

{data.user && }
); }

Key Points:

  • Parent spreads ...UserCard_user to include fragment data
  • Parent passes opaque data.user reference to child
  • Child uses useFragment to access actual fields
  • Parent never directly accesses name, email, etc. (data masking)

Example 2: Nested Fragment Composition

Components can compose fragments of other components:

Avatar.jsx (Leaf Component):

import { useFragment, graphql } from 'react-relay';

export default function Avatar({ userRef }) {
  const user = useFragment(
    graphql`
      fragment Avatar_user on User {
        avatarUrl
        name
      }
    `,
    userRef
  );

  return {user.name};
}

UserInfo.jsx (Middle Component):

import { useFragment, graphql } from 'react-relay';
import Avatar from './Avatar';

export default function UserInfo({ userRef }) {
  const user = useFragment(
    graphql`
      fragment UserInfo_user on User {
        name
        email
        memberSince
        ...Avatar_user  # ← Compose Avatar's fragment
      }
    `,
    userRef
  );

  return (
    
{/* Pass fragment ref down */}

{user.name}

{user.email}

Member since {user.memberSince}
); }

ProfilePage.jsx (Top Component):

import { useLazyLoadQuery, graphql } from 'react-relay';
import UserInfo from './UserInfo';

export default function ProfilePage() {
  const data = useLazyLoadQuery(
    graphql`
      query ProfilePageQuery($id: ID!) {
        user(id: $id) {
          ...UserInfo_user  # ← Single spread includes all nested fragments
        }
      }
    `,
    { id: '123' }
  );

  return ;
}

Component Tree:

ProfilePage (Query)
    β”‚
    ↓ ...UserInfo_user
UserInfo (Fragment)
    β”œβ”€ Renders: name, email, memberSince
    β”‚
    ↓ ...Avatar_user
Avatar (Fragment)
    └─ Renders: avatarUrl, name

πŸ’‘ Pattern Insight: Each component spreads child fragments into its own fragment. The query at the top automatically includes all nested requirements.

Example 3: Lists with Plural Fragments

When rendering lists, use the @relay(plural: true) directive:

CommentItem.jsx:

import { useFragment, graphql } from 'react-relay';

export default function CommentItem({ commentRef }) {
  const comment = useFragment(
    graphql`
      fragment CommentItem_comment on Comment {
        id
        text
        author {
          name
          avatarUrl
        }
        createdAt
      }
    `,
    commentRef
  );

  return (
    
{comment.author.name}

{comment.text}

); }

CommentList.jsx:

import { useFragment, graphql } from 'react-relay';
import CommentItem from './CommentItem';

export default function CommentList({ commentsRef }) {
  const comments = useFragment(
    graphql`
      fragment CommentList_comments on Comment @relay(plural: true) {
        id
        ...CommentItem_comment
      }
    `,
    commentsRef  # ← Array of fragment references
  );

  return (
    
{comments.map(comment => ( ))}
); }

Parent Query:

const data = useLazyLoadQuery(
  graphql`
    query PostQuery($postId: ID!) {
      post(id: $postId) {
        title
        comments {
          ...CommentList_comments  # ← Array spread
        }
      }
    }
  `,
  { postId: '456' }
);

return ;

Type Safety with Arrays:

import type { CommentList_comments$key } from './__generated__/CommentList_comments.graphql';

type Props = {
  commentsRef: CommentList_comments$key;  // ← Array type (ReadonlyArray)
};

⚠️ Remember: Use @relay(plural: true) when your fragment is for an array. Without it, TypeScript types will be wrong!

Example 4: Conditional Fragment Fields

Handle optional data with nullable fields:

import { useFragment, graphql } from 'react-relay';

export default function UserProfile({ userRef }) {
  const user = useFragment(
    graphql`
      fragment UserProfile_user on User {
        name
        bio
        website  # ← Might be null
        company {
          name
          logo
        }  # ← Entire object might be null
      }
    `,
    userRef
  );

  return (
    

{user.name}

{user.bio &&

{user.bio}

} {user.website && ( Visit Website )} {user.company && (
Works at {user.company.name}
)}
); }

πŸ’‘ Best Practice: Always handle nullability in your render logic. Relay's generated types will mark nullable fields appropriately.

Common Mistakes and How to Avoid Them ⚠️

Mistake 1: Accessing Data Without useFragment

❌ Wrong:

function UserCard({ user }) {
  // Trying to access data directly from fragment reference
  return <div>{user.name}</div>;  // TypeError: Cannot read property 'name'
}

βœ… Correct:

function UserCard({ userRef }) {
  const user = useFragment(
    graphql`fragment UserCard_user on User { name }`,
    userRef
  );
  return <div>{user.name}</div>;  // Works!
}

Why: Fragment references are opaque tokens. Only useFragment can extract actual data.

Mistake 2: Wrong Fragment Naming

❌ Wrong:

// Fragment name doesn't match component + prop pattern
fragment MyUserFragment on User { name }  // ❌
fragment userData on User { name }        // ❌
fragment user_UserCard on User { name }   // ❌ (reversed)

βœ… Correct:

// Component: UserCard, Prop: userRef
fragment UserCard_user on User { name }   // βœ…

Fix: Always use <ComponentName>_<propName> format exactly.

Mistake 3: Forgetting to Spread Fragments

❌ Wrong:

const data = useLazyLoadQuery(
  graphql`
    query AppQuery {
      user {
        id
        name  # ← Querying fields directly, not spreading fragment
      }
    }
  `
);

return <UserCard userRef={data.user} />;  // ❌ Fragment data missing!

βœ… Correct:

const data = useLazyLoadQuery(
  graphql`
    query AppQuery {
      user {
        ...UserCard_user  # ← Spread the fragment
      }
    }
  `
);

return <UserCard userRef={data.user} />;  // βœ…

Mistake 4: Missing @relay(plural: true)

❌ Wrong:

fragment CommentList_comments on Comment {  // ← Missing plural directive
  id
  text
}

// TypeScript type will be wrong (single object, not array)
const comments = useFragment(..., commentsRef);  // Type error!

βœ… Correct:

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

const comments = useFragment(..., commentsRef);  // Correctly typed as array

Mistake 5: Mutating Fragment Data

❌ Wrong:

const user = useFragment(...);
user.name = "New Name";  // ❌ Data is read-only!

βœ… Correct:

const user = useFragment(...);
// Use mutations to update data
commitMutation(environment, {
  mutation: graphql`mutation { updateUser(...) }`,
  variables: { name: "New Name" }
});

Why: Fragment data is immutable. Use Relay mutations to update the store.

Mistake 6: Prop Name Mismatch

❌ Wrong:

// Fragment uses "user"
fragment UserCard_user on User { name }

// But component expects "userData"
function UserCard({ userData }) {  // ❌ Naming inconsistency
  const user = useFragment(..., userData);
}

βœ… Correct:

fragment UserCard_user on User { name }

function UserCard({ userRef }) {  // βœ… Matches fragment: _user β†’ userRef
  const user = useFragment(..., userRef);
}

Convention: If fragment is Component_foo, prop should be fooRef.

Key Takeaways 🎯

πŸ“‹ Quick Reference Card

Hook Signature useFragment(fragmentDef, fragmentRef)
Fragment Naming ComponentName_propName (enforced)
Data Masking Parents see opaque refs, not actual data
Type Pattern ComponentName_propName$key for refs
Arrays Add @relay(plural: true) directive
Null Handling useFragment returns null for null refs
Composition Spread child fragments: ...ChildComponent_prop
Immutability Fragment data is read-only, use mutations to change

Mental Model: Fragment references are like locked boxes – useFragment is the only key that opens them. This ensures components can't accidentally depend on data they didn't request.

Core Principles to Remember 🧠

  1. Colocate Data Requirements: Define fragments in the same file as the component that uses them
  2. Single Source of Truth: Each component owns its data requirements, not its parent
  3. Compose, Don't Duplicate: Spread child fragments instead of re-declaring fields
  4. Type Safety First: Use generated TypeScript types for all fragment references
  5. Trust Data Masking: Let Relay enforce isolation – it prevents bugs at scale

When to Use useFragment vs Other Hooks

Use Case Hook Reason
Reading fragment data in component useFragment Standard pattern for all components
Root query at page/route level useLazyLoadQuery Initiates data fetching
Preloaded data from router usePreloadedQuery Avoids fetch waterfalls
Paginated lists usePaginationFragment Handles cursors automatically
Refetchable data useRefetchableFragment Adds refetch capability

πŸ’‘ Rule of Thumb: Start with useFragment for 90% of components. Upgrade to specialized hooks only when you need their specific features.

πŸ“š Further Study


Next Steps: Now that you understand useFragment, explore how to handle pagination with usePaginationFragment and real-time updates with subscriptions. Fragment composition patterns will become second nature as you build more complex Relay applications!