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:
- Fragment definition (created with
graphqltagged template literal) - 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.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_userto include fragment data - Parent passes opaque
data.userreference to child - Child uses
useFragmentto 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
;
}
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 π§
- Colocate Data Requirements: Define fragments in the same file as the component that uses them
- Single Source of Truth: Each component owns its data requirements, not its parent
- Compose, Don't Duplicate: Spread child fragments instead of re-declaring fields
- Type Safety First: Use generated TypeScript types for all fragment references
- 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
- Relay Documentation: Fragment Containers - Official API reference and advanced patterns
- Type-Safe Relay: TypeScript Integration - Deep dive into generated types and configuration
- Data Masking Benefits: Relay Compiler Architecture - Understanding how Relay achieves isolation
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!