Repeat data in child fragments

Using the client-preset and React and following this article Unleash the power of Fragments with GraphQL Codegen | Hive I have a question about displaying the same field in multiple components as it traverses down the tree

Let’s say we have this structure (whether good or bad)

              APP
               |
            Customer
               |
            Details

Inside of Customer and Details I display the first and last name.

Should my fragment inside of customer include first and last name? Or just in the Details fragment and then spread it inside of the customer fragment?

My thoughts is that I may want it in both places because I’m rendering it in both places so its nice to know exactly which components are using those fields. Is this an issue with duplication in the queries?

Hey @BrennenRocks :waving_hand:

What version of Apollo Client are you using? I ask because if you’re using 3.12 or later, I highly recommend not using GraphQL Codegen’s fragment masking features and instead use Apollo Client’s data masking feature. Data masking adds additional performance benefits since it can handle fine-grained rerenders by only rerendering components who’s data has changed.

That said, let me also answer more generically :slightly_smiling_face:

First off, I highly recommend taking a look at our fragments guide which goes into detail on how we view using fragments in Apollo Client. Generally we recommend colocating your fragments with your components and having those fragments only ask for the data they need. From the colocating fragments section, we provide this tip:

To prevent coupling with deeply nested components, we recommend parent components only add fragments defined by their directly-rendered children. In this example, our FeedPage should not use the EntryInfoFragment or VoteButtonsFragment directly. Instead the FeedPage uses the FeedEntryFragment fragment colocated with the FeedEntry component to combine the VoteButtonsFragment and EntryInfoFragment fragments into its own fragment.

So to answer this question:

Inside of Customer and Details I display the first and last name.

Should my fragment inside of customer include first and last name? Or just in the Details fragment and then spread it inside of the customer fragment?

As long as Customer consumes first and last name, yes you should include it. If only Details consumes those fields, Customer should not consume it. Again, I’d highly recommend looking at using data masking as it makes these decisions easy for you since you’d only be able to access fields that the component has asked for :slightly_smiling_face: . With this enabled, you’d need to defined it in both fragments in order for each component to access that data.

Is this an issue with duplication in the queries?

Nope! GraphQL flattens fragments before evaluating the selection sets so duplicating fields between fragments is not an issue at all. If you’re into the nitty gritty algorithmic details, check out the spec on executing selection sets which hopefully provides further insight: GraphQL

If you’d like a more real world example of how we use and recommend fragment colocation, check out our Spotify showcase which has a lot of these patterns in it.

Hope this helps!

1 Like

Oh and let me mention one more “why” on why you’d want Customer to include those fields, read the introduction on the data masking section which describes the kind of bugs you can introduce when components don’t request the data they need. I think this lays out a similar scenario to what you’re describing and what could happen as requirements/components change. Let me know if you have any more questions!

Thank you so much for the great explanation!

We are on the latest Apollo Client but I am using GraphQL Codegen’s useFragmentData to do this. Let me share some of my code and I would love a code review from you for this simple example :stuck_out_tongue:

I feel like I’ve gone far enough using GraphQL Codegen’s fragment stuff where I’m pretty locked in at the moment and it might take me longer than I would like to switch to just Apollo Client’s fragments

import { graphql } from '@/__gql__';


const Deal_Query = graphql(/* GraphQL */ `
  query Deal_Query($dealId: Int!) {
    deal(id: $dealId) {
      id
      customer {
        id
        first_name
        last_name
      }
      ...DriverInfoFragment
    }
  }
`);

...Preloading...
...Component Declaration...

  const dealQueryRef = Route.useLoaderData();
  const { data } = useReadQuery(dealQueryRef);

...Render...
// display customer first and last name in some Deal header component

{data.deal && <DriverInfo driverInfo={data.deal} />}

...

driver-info.tsx

import { getFragmentData, graphql, type FragmentType } from '@/__gql__';


const DriverInfoFragment = graphql(/* GraphQL */ `
  fragment DriverInfoFragment on deal {
    id

...Other Fields...

    customer {
      id
      first_name
      last_name
      address {
        id
        city
        state
      }
    }
  }
`);


type DriverInfoProps = ComponentProps<'div'> & {
  driverInfo: FragmentType<typeof DriverInfoFragment>;
};

export default function DriverInfo({ driverInfo, className, ...props }: DriverInfoProps) {
  const { customer } = getFragmentData(
    DriverInfoFragment,
    driverInfo,
  );

... Display customer info ...

This is getting and displaying first and last name in both parent and child components. I’m fairly satisfied with what this has allowed me to do already. Granted I haven’t read too much about Apollo’s Fragments since I already went deep into GraphQL Codegen’s fragments.

After seeing this code would you still recommend the switch? Does Apollo handle fine-grained rerenders better than GraphQL’s implementation?

After seeing this code would you still recommend the switch? Does Apollo handle fine-grained rerenders better than GraphQL’s implementation?

Yes! Mostly due to the performance front. At the very least, I’m glad you’re using the fragment colocation pattern :slightly_smiling_face:. We are making more of a push to make this the default way to write apps with Apollo Client.

GraphQL Codegen’s useFragment “hook” (its actually just a function, not a hook) is purely a TypeScript helper function. At runtime, its just an identity function. Here is the output of useFragment when I run it in the spotify showcase repo:

// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is undefined
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | undefined
): TType | undefined;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null
): TType | null;
// return nullable if `fragmentType` is nullable or undefined
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType:
    | FragmentType<DocumentTypeDecoration<TType, any>>
    | null
    | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType: Array<FragmentType<DocumentTypeDecoration<TType, any>>>
): Array<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType:
    | Array<FragmentType<DocumentTypeDecoration<TType, any>>>
    | null
    | undefined
): Array<TType> | null | undefined;
// return readonly array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return readonly array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType:
    | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
    | null
    | undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType:
    | FragmentType<DocumentTypeDecoration<TType, any>>
    | Array<FragmentType<DocumentTypeDecoration<TType, any>>>
    | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
    | null
    | undefined
): TType | Array<TType> | ReadonlyArray<TType> | null | undefined {
  return fragmentType as any;
}

You’ll see this is just a bunch of function overloads that handle various TypeScript types. This is the key part of the runtime though:

export function useFragment<TType>(
  _documentNode: DocumentTypeDecoration<TType, any>,
  fragmentType:
    | FragmentType<DocumentTypeDecoration<TType, any>>
    | Array<FragmentType<DocumentTypeDecoration<TType, any>>>
    | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
    | null
    | undefined
): TType | Array<TType> | ReadonlyArray<TType> | null | undefined {
  return fragmentType as any;
}

Notice it just returns the input unmodified so its just an identity function. This is why I put “hook” in quotes and mentioned its just a function (as I’m typing this, I realized you changed the name to getFragmentData which is fantastic so you don’t confuse it with a hook :slightly_smiling:).

Since its just an identity function, this means that updates to data in those fragments will render the query component as well. This is probably fine for most cases, but it means you’re re-rendering components that don’t use or consume that data and are just passing that data down to child components.

Apollo’s useFragment hook (combined with data masking) instead won’t rerender your query component and will only rerender the useFragment components that are affected by that change. Thats going to be the biggest difference in performance.

That said, I’d be remiss if I didn’t mention a few other differences between the two. Apollo’s useFragment hook has a few limitations due to how it works at runtime that otherwise “just work” with GraphQL Codegen’s useFragment function since its just an identity function.

  • You can’t use arrays currently with useFragment(meaning the from option only accepts a single record). We’ll be addressing this in a 4.x release :slightly_smiling_face:
  • The parent MUST select any keyFields available to the record you’re providing to useFragment. useFragment is technically a cache API so it requires that it can identify the object in the cache. This also means:
    • You can’t currently use it with non-normalized data
    • You can’t use it with no-cache queries

If you can, I’d recommend using Apollo’s useFragment hook moving forward and try to migrate over time away from codgen’s fragment masking feature. Almost all of the same patterns apply between GraphQL Codegen and Apollo Client with the way you’re already using it (with the exception of the things I mentioned above). The biggest difference will be the function signature between the two.

I forgot to mention this as well, but I’d recommend reading this blog post by Alessia Bellesario (former Apollo Client mantainer) on the useFragment hook which does a great job showcasing the performance differences: Don’t Overreact! Introducing Apollo Client’s new @nonreactive directive and useFragment hook | Apollo GraphQL Blog

Note that article was written before data masking existed. You can think of data masking like automatically applying that @nonreactive directive everywhere a fragment spread is used. But @nonreactive might be helpful if you decide to start moving toward Apollo Client’s useFragment hook and still want some of the perf benefits without doing a full migration.

If data masking interests you, we have a full incremental migration guide that walks you through how to adopt it.

Does this help?

This helps tremendously. Very valuable information because I didn’t understand the differences between Apollo and GraphQL Codegen fragments.

Is this still necessary with Apollo?

export const FEED_ENTRY_FRAGMENT = gql`

  fragment FeedEntryFragment on FeedEntry {

    commentCount

    repository {

      full_name

      html_url

      owner {

        avatar_url

      }

    }

    ...VoteButtonsFragment

    ...EntryInfoFragment

  }

  ${VOTE_BUTTONS_FRAGMENT} <---- including this?
 
  ${ENTRY_INFO_FRAGMENT} <---- including this?

`

And registering them with the fragment registry? When I was initially reading the docs between the two this is what put me off from Apollo. However if this is still necessary it might be worth the tradeoff to me for removing React Context like rerendering issues hahah

  • We’re already practicing fetching keyFields in parent queries
  • We use normalized data
  • we have no queries that have no-cache

So I think we’re on our way to being able to migrate. The only real benefit I see at the moment is the difference between the useFragment hooks and the rerendering at the query level (pretty huge benefit tbh)

Is this still necessary with Apollo?

Yep. You’ll either need that interpolation of fragments, or you’ll need to use the fragment registry. Apollo doesn’t have a compiler so it needs to be able to construct the full query, including the fragment definitions, at runtime. If you don’t use either of those techniques, Apollo Client (or your server for that matter) won’t know what selections are included in those fragments and you’ll get an error.

If you’d like to use the fragment registry (which I find very helpful), check out fragment registry guide. You won’t need to add the interpolated fragments in parent queries/fragments when using it since the fragment registry will fill those definitions in when the query gets executed. There is really only one caveat when using it, which is addressed in the documentation:

You need to register fragment definitions with the fragment registry before executing operations that use them. This can be problematic when lazy loading component files because the application might not register the fragment definition with the registery until after the query begins executing. Move the fragment definition to a shared file that isn’t lazy-loaded.

I think you’re fine though with the way it seems you’re using it. On that note, I’d skip some of the sub-sections there and go straight to “Lazily registering named fragments”. I’ve found this pattern to be useful and I think you’ll like the DX of that. This is the pattern we use in the spotify showcase (example) and it worked quite well there. The parent can just refer to the fragment by name (example).

By the way, the fragment registry is global, so no need for React Context or anything to make this work :slightly_smiling_face:

  • We’re already practicing fetching keyFields in parent queries

  • We use normalized data

  • we have no queries that have no-cache

That’s fantastic to hear! I think moving over to useFragment then should be fairly straightforward :slightly_smiling_face:. Let me know how that goes!

1 Like

We do not recommend using the fragment registry when using the graphql function generated by the GraphQL Codegen client preset. The client preset creates precompiled GraphQL documents that already include fragment definitions.

Could I still use useFragment from Apollo and the client-preset from GraphQL Codegen and not have to use the string interpolation or the fragment registry?

Yep! The fragment option accepts a DocumentNode (the runtime value representing the parsed GraphQL AST, not the DocumentNode type itself) which is what is returned by that graphql function, so totally fine. That precompiled document includes any selection sets from fragment spreads, so the fragment registry just adds additional runtime overhead that you don’t need, hence the disclaimer.