Prepared Docs

Prepared Docs

⌘K

    Loading navigation…

User

  1. Working With Data
  2. Graphql Fragments

GraphQL Fragments

Using fragments for type-safe data sharing and component composition

Fragments capture the data contract for a component. Think of them as the schema that a component owns, rather than a shared utility to sprinkle everywhere. Our approach uses direct fragment type access—no runtime masking, just compile-time type safety scoped to the component consuming the fragment.

Fragments build on operations to provide type-safe, reusable field selections. They capture the data contract for a component and generate TypeScript types you can use directly in props.

Direct Fragment Type Access

We use fragments to generate TypeScript types that can be used directly in component props:

This generates a TypeScript type that you import and use directly:

Creating and Using Fragments

Step 1: Define the Fragment

Create a fragment file next to your component:

Step 2: Use in a Query

Import and use the fragment in your operations:

Step 3: Type Your Components

Fragment Organization

Component-Specific Fragments

For fragments used by a single component:

Fragment Composition

Fragment composition is the core pattern for scaling a fragmentized component tree. Instead of one large fragment that owns every field, break the fragment into focused child fragments—one per logical concern—and compose them back into the parent via ...Spread. A single query at the route level fetches all the data, and typed props flow downward through the component tree.

How to compose fragments

Define each child fragment in the file closest to the component that consumes it, then spread it into the parent fragment:

Child components receive their data via typed props—they do not make their own useQuery calls:

Breaking up a large fragment

When a shared fragment grows too large, the right approach is to extract focused child fragments and compose them back—not to move fields into separate queries. This improves ownership and readability while preserving the single-query model. (It does not reduce payload by itself unless route-level queries stop spreading unused child fragments.) See the Query Splitting anti-pattern below for what to avoid.

  1. Extract the fields into a new focused fragment file next to its consumer
  2. Spread ...ChildFragment back into the parent fragment
  3. Update child component props to accept the focused fragment type

Before and After Examples

❌ Don't do this - Using Query Types Directly

Problem: Drilling into query types without fragments

Issues:

  • Fragile: Query structure changes break components
  • No reusability: Can't share this type
  • Poor semantics: Type doesn't describe its purpose

❌ Don't do this - Manual Type Assertions

Problem: Creating types manually or using assertions

✅ Do this - Fragment Types

Solution: Use generated fragment types

❌ Don't do this - Query Splitting

Problem: Removing fields from a fragment and replacing them with separate useQuery calls in each component

When a fragment feels too large, it's tempting to move individual fields into dedicated queries so each component fetches only what it needs. This is the query splitting anti-pattern:

Issues:

  • Multiple round-trips: Every component mounts a separate network request for the same entity
  • Duplicate server work: The resolver for chatroom(id:) runs once per query
  • Cache fragmentation: Fields for the same entity arrive at different times, causing inconsistent render states
  • Subscription complexity: Real-time updates must now invalidate or re-subscribe to N separate queries instead of one
  • Apollo deduplication does not help: Deduplication only applies to identical query documents with identical variables: different query documents for the same chatroom(id:) are each a distinct request

✅ Do this - Compose Smaller Fragments

Solution: Extract focused child fragments and compose them back into the parent—keep a single query at the route level

Migrating from Query Type Extractions

A common anti-pattern is extracting types directly from query results using TypeScript's indexed access types. This creates fragile, hard-to-maintain code that breaks when query structure changes.

❌ Don't do this - Query Type Extraction

Problem: Extracting types from query results using indexed access

Issues:

  • Fragile: Query structure changes break component types
  • Hard to discover: Types are hidden in query result structure
  • No reusability: Can't share these types across components
  • Poor semantics: Type path doesn't describe its purpose
  • Maintenance burden: Must update multiple places when query changes
  • Nullability issues: Doesn't handle nullable query results safely

⚠️ Partial Improvement - Null-Safe Extraction

Better, but not ideal: Using NonNullable to handle nullable query results

Benefits over raw extraction:

  • Handles nullability: Safely handles cases where query results might be null
  • Type safety: Prevents accessing properties on potentially null values
  • Better than raw extraction: More defensive than direct indexed access

Limitations:

  • Still fragile: Query structure changes still break component types
  • Still hard to discover: Types are still hidden in query result structure
  • No reusability: Still can't share these types across components
  • Poor semantics: Type path still doesn't describe its purpose
  • Complex syntax: Nested NonNullable calls become hard to read

When to use: This is acceptable as a temporary step during migration, but you should still migrate to fragments for the full benefits.

✅ Do this - Fragment Types

Solution: Create fragments and use generated fragment types

Step 1: Create Fragment Files

Create fragment files next to your components or in a shared fragments directory:

Step 2: Update Query to Use Fragments

Update your query to use the fragments instead of inline fields:

Step 3: Update TypeScript Types

Replace query type extractions with fragment types:

Step 4: Update Component Usage

Components now use the fragment types directly:

Migration Benefits

After migrating to fragments:

  • Type safety: Types are generated from GraphQL schema, not query structure
  • Discoverability: Fragment types are easy to find and import
  • Reusability: Fragments can be shared across multiple queries and components
  • Maintainability: Update fragment once, types update everywhere
  • Semantic clarity: Fragment names describe their purpose (AudioCollectorDeviceData vs Query["dispatchCenter"]["audioCollectorStatusReports"][0])

Type Safety Benefits

Compile-Time Checking

Refactoring Safety

When you add or remove fields from a fragment:

  1. Update the fragment definition
  2. Run codegen
  3. TypeScript immediately shows where updates are needed

Fragment Best Practices

✅ Do

  • Name fragments clearly: UserCardData, ChatroomListItem
  • Colocate with components: Keep fragments near their primary consumer
  • Use for component props: Type props with fragment types

❌ Don't

  • Over-fragment: Don't create fragments for every tiny selection
  • Use query types directly: Always extract fragments for component data
  • Forget the spread: Remember to use ...FragmentName in queries
  • Request unused fields: ESLint will warn about unused fragment fields
  • Split into separate queries: When a fragment grows large, extract child fragments and compose them: don't move fields into standalone useQuery calls per component (see Query Splitting anti-pattern above)

Advanced Patterns

Conditional Fragments

Use fragments with inline fragments for polymorphic types:

Fragment Arrays

When working with lists:

When list items re-render too broadly because the parent passes new object references on every Apollo cache update (for example after a subscription writes many normalized entities), see Reading fragments from cache with useFragment below.

Reading fragments from cache with useFragment

Apollo Client’s useFragment reads a specific fragment for an entity already in the normalized cache. It creates a cache subscription: the component re-renders only when fields in that fragment change for that id, not when unrelated cache keys update. See the Fragments guide for Apollo’s full documentation on the hook.

Use this pattern for list rows, map markers, or any repeated UI where the parent maps query edges to children and would otherwise pass full objects—so every child gets a new reference whenever the parent re-renders after a wide cache write.

When to use

  • The parent lists many entities of the same type (Chatroom, etc.).
  • Global or list-level subscriptions (e.g. CommunicationsListUpdated) update the cache for many chatrooms at once.
  • You want React.memo on the row/marker to skip re-renders unless that row’s fragment data actually changed.

Pattern

  1. Parent passes a stable primitive (e.g. chatroomId={listItem.id}) plus route props (to, active), not the full chatroom object.
  2. Child shell calls useFragment with from: { __typename: "Chatroom", id: chatroomId } and the generated *FragmentDoc that matches what list queries already write (e.g. ChatroomListItemDataFragmentDoc, ChatroomMapFragmentDoc).
  3. If there is no usable row in cache yet, return null (or a skeleton) from the shell.
  4. Body component (wrapped in React.memo) receives the resolved fragment data as props and runs other hooks (useShareIndicators, etc.).

Shell / body split (rules of hooks)

You cannot call hooks after an early return. So split the component:

  • Shell: only useFragment (and optionally nothing else), then guard on if (!complete || !data?.id) return null, then render the body.
  • Body: memo’d; all remaining hooks live here.

Incorrect (every row re-renders when the parent’s mapped data gets new references)

Correct (stable chatroomId + useFragment + memo)

Map markers

The same idea applies to pins that previously received a full ChatroomMap object from useChatroomMapList: pass chatroomId, read ChatroomMapFragmentDoc in a shell, render a memoized body that builds the marker and tooltip.

Typing

useFragment returns { complete, data, missing }. The complete boolean tells you whether every field in the fragment was found in cache. When complete is false, data may contain partial fields (MaybeMasked<TData> | DataValue.Partial<MaybeMasked<TData>> in Apollo’s types). Guard with if (!complete || !data?.id) return null, then cast to your concrete fragment type (e.g. data as ChatroomListItemData) so downstream code stays strict.

How this fits the rest of the doc

  • Fragment Arrays above shows the simpler “pass objects from the query” shape; use useFragment when cache churn causes unnecessary re-renders.
  • Real-World Example below shows BroadcastableChatroomListData and list composition; subscription payloads often merge into those fields—useFragment rows subscribe to the merged cache slice per id.
  • Centralizing noisy subscriptions (e.g. a single ChatroomEventCreated in ChatroomContextProvider) reduces duplicate WebSocket work; useFragment on list rows reduces React re-render blast radius when list cache updates still fire often. The two are complementary.

Real-World Example

From our codebase, here's how the chatroom list uses fragments:

This fragment is then composed in other fragments:

And used in queries:

Common Pitfalls

Fragment Import Paths

Always import from .graphql files (Vite resolves to .graphql.ts automatically):

Fragment Naming Conflicts

Ensure fragment names are unique across your application. Use descriptive names that include the component context:

For day-to-day development workflow, debugging, and troubleshooting, see the Development Workflow guide.

Previous

Working with data / GraphQL Colocation

Next

Working with data / GraphQL Development Workflow

On this page

Direct Fragment Type Access
Creating and Using Fragments
Step 1: Define the Fragment
Step 2: Use in a Query
Step 3: Type Your Components
Fragment Organization
Component-Specific Fragments
Fragment Composition
How to compose fragments
Breaking up a large fragment
Before and After Examples
❌ Don't do this - Using Query Types Directly
❌ Don't do this - Manual Type Assertions
✅ Do this - Fragment Types
❌ Don't do this - Query Splitting
✅ Do this - Compose Smaller Fragments
Migrating from Query Type Extractions
❌ Don't do this - Query Type Extraction
⚠️ Partial Improvement - Null-Safe Extraction
✅ Do this - Fragment Types
Step 1: Create Fragment Files
Step 2: Update Query to Use Fragments
Step 3: Update TypeScript Types
Step 4: Update Component Usage
Migration Benefits
Type Safety Benefits
Compile-Time Checking
Refactoring Safety
Fragment Best Practices
✅ Do
❌ Don't
Advanced Patterns
Conditional Fragments
Fragment Arrays
Reading fragments from cache with useFragment
Real-World Example
Common Pitfalls
Fragment Import Paths
Fragment Naming Conflicts