Unleash the power of Fragments with GraphQL Codegen
Tags: graphql, codegen, react, typescript
Published: 2022-08-30 08:46:37

> This article was published on Friday, August 5, 2022 by [Laurin Quast](https://twitter.com/n1rual) @ [The Guild Blog](https://the-guild.dev/)

There is no doubt that Relay is the most advanced JavaScript GraphQL Client out there. However, at
the same time, the learning curve and adoption is yet very sparse compared to other popular
alternatives such as Apollo Client, urql, or GraphQL Request.

As a consequence, many people don't even know about all the benefits and patterns used within (and
so far being exclusive to) Relay. The Guild gathered and worked on projects whose GraphQL client
usage spans across all the available solutions today. In our opinion, the most important parts of
Relay are the concepts of building and scaling applications. These patterns are actually applicable
to any GraphQL Client, you just need the right code generation tool for the job.

On a high level these benefits are the following:

**A fragmentized component tree.** Instead of writing a single GraphQL operation document per
visible component or extracting the component properties from a single query operation document
type, which is often cumbersome), multiple fragment definitions can be composed up to a single query
operation that is sent to the server. This reduces the amount of concurrent requests and together
with `@defer` and `@stream` is an actual improvement in the performance of the application
([compared to batching GraphQL operations](https://apollographql.com/blog/apollo-client/performance/batching-client-graphql-queries)).

![Fragmentized Component Tree](https://the-guild.dev/blog-assets/unleash-the-power-of-fragments-with-graphql-codegen/component-tree-diagram.png)

**Colocate GraphQL documents with the component code.** Instead of writing GraphQL operations in a
dedicated file, the operations are written within the component code. Have you ever deleted
component code and forgot to delete the corresponding operation or fragment definitions that are in
some other field? We encountered this issue over and over again generating a lot of dead code.

```tsx
import { FragmentType, graphql, useFragment } from './gql'

const Avatar_UserFragment = graphql(/* GraphQL */ `
  fragment Avatar_UserFragment on User {
    avatarUrl
  }
`)

type AvatarProps = {
  user: FragmentType<typeof Avatar_UserFragment>
}

export function Avatar(props: AvatarProps) {
  const user = useFragment(Avatar_UserFragment, props.user)
  return <CircleImage src={user.avatarUrl} />
}
```

**Data (fragment) masking.** Ensure that a component can only access the data defined in its
fragment definitions in order to keep the component a self-contained building block that can be
re-used.

> We are happy to announce that we now have a preset for bringing the above Relay patterns to your
> existing projects, no matter what client library you are currently using, without having to switch
> over and commit to the Relay ecosystem.

## A New GraphQL Code Generator Preset

The new preset serves as a drop-in replacement for all the plugins that generate hooks for existing
clients such as urql and apollo-client.

```bash
yarn add -D @graphql-codegen/client-preset
```

```ts
import { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
  documents: ['src/**/*.tsx', '!src/gql/**/*'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: []
    }
  }
}

export default config
```

Instead of generating the hooks for the existing clients, the preset works with function signature
overloading and [`TypedDocumentNode`](/blog/typed-document-node)) for inferring the correct GraphQL
operation and variables type.

> You can learn more about `TypedDocumentNode` in
> [graphql.wtf episode #41](https://youtube.com/watch?v=cYIhx8dusa4).

In your application code you will now effectively write the following code.

```tsx
import { useQuery } from 'urql'
import { graphql } from './gql/gql'

const ViewerQueryDocument = graphql(/* GraphQL */ `
  query ViewerQuery {
    viewer {
      id
      name
    }
  }
`)

const Viewer = () => {
  const [result] = useQuery({ query: ViewerQueryDocument })
  const { data, fetching, error } = result

  if (fetching) return <p>Loading…</p>
  if (error) return <p>Oh no… {error.message}</p>
  // data is fully typed!
  return <p>{data?.viewer?.name}</p>
}
```

Because the client has built in support for TypedDocumentNode, it will extract the correct operation
and variables type from the document passed to `useQuery`.

[Head over to the GraphQL Code Generator documentation](https://graphql-code-generator.com/plugins/presets/client-preset)
for more details.

> This is now our recommended way of using GraphQL Code Generator for front end development.

## Describe Component Data Needs via GraphQL Fragments

Instead of writing one big GraphQL Operation for our whole page and passing that down to the
components, start with describing the component's data dependencies through a GraphQL fragment. This
way, you are making the data-dependency of your component colocated and explicit in the same way
that some would colocate the TypeScript definitions or CSS if you are using the styled components
pattern.

```tsx
import { FragmentType, graphql, useFragment } from './gql'

const Avatar_UserFragment = graphql(/* GraphQL */ `
  fragment Avatar_UserFragment on User {
    avatarUrl
  }
`)

type AvatarProps = {
  user: FragmentType<typeof Avatar_UserFragment>
}

export function Avatar(props: AvatarProps) {
  const user = useFragment(Avatar_UserFragment, props.user)
  return <CircleImage src={user.avatarUrl} />
}
```

## Restrict Data Access with Fragments

By utilizing the `useFragment` hook we ensure that the component can only access the properties of
the data that are declared directly within the fragment selection set. The other data declared
through the additional fragment spread(`Avatar_UserFragment`) cannot be accessed within the
`UserListItem` .

The following example would raise a TypeScript error, as the `avatarUrl` field is not selected
within the `UserListItem_UserFragment` fragment.

```tsx
import { Avatar } from './avatar'
import { FragmentType, graphql, useFragment } from './gql'

const UserListItem_UserFragment = graphql(/* GraphQL */ `
  fragment UserListItem_UserFragment on User {
    fullName
    ...Avatar_UserFragment
  }
`)

type UserListItemProps = {
  user: FragmentType<typeof UserListItem_UserFragment>
}

export function UserListItem(props: UserListItemProps) {
  const user = useFragment(UserListItem_UserFragment, props.user)

  // ERROR: Property 'avatarUrl' does not exist on type
  const icon = <img src={user.avatarUrl} />
  return <ListItem icon={icon}>{user.fullName}</ListItem>
}
```

## Compose Fragments for UI Components

As we compose simple visual UI components into bigger functional UI components we can also compose
the fragments of the consumed UI component.

```tsx
import { Avatar } from './avatar'
import { FragmentType, graphql, useFragment } from './gql'

const UserListItem_UserFragment = graphql(/* GraphQL */ `
  fragment UserListItem_UserFragment on User {
    fullName
    ...Avatar_UserFragment
  }
`)

type UserListItemProps = {
  user: FragmentType<typeof UserListItem_UserFragment>
}

export function UserListItem(props: UserListItemProps) {
  const user = useFragment(UserListItem_UserFragment, props.user)
  return <ListItem icon={<Avatar user={user} />}>{user.fullName}</ListItem>
}
```

## Compose Fragment Components for Your Top-Level Route or View

We can now further compose our components till we reach those components that select the root Query
object type.

```tsx
import { FragmentType, graphql, useFragment } from './gql'
import { UserListItem } from './user-list-item'

const FriendList_QueryFragment = graphql(/* GraphQL */ `
  fragment FriendList_QueryFragment on Query {
    friends(first: 5) {
      id
      ...UserListItem_UserFragment
    }
  }
`)

type FriendListProps = {
  query: FragmentType<typeof FriendList_QueryFragment>
}

export function FriendList(props: FriendListProps) {
  const query = useFragment(FriendList_QueryFragment, props.query)
  return (
    <List>
      {query.friends.map(user => (
        <FriendListItem key={user.id} user={user} />
      ))}
    </List>
  )
}
```

Earlier, we declared the `Avatar` component with the `Avatar_UserFragment` fragment. We can now
re-use this Avatar in a different context by again spreading the fragment and then passing the
relevant data to the `Avatar` component `user` property.

```tsx
import { FragmentType, graphql, useFragment } from './gql'

const UserProfileHeader_QueryFragment = graphql(/* GraphQL */ `
  fragment UserProfileHeader_QueryFragment on Query {
    viewer {
      id
      homeTown
      registeredAt
      ...Avatar_UserFragment
    }
  }
`)

type UserProfileHeaderProps = {
  query: FragmentType<typeof UserProfileHeader_QueryFragment>
}

export function UserProfileHeader(props: UserProfileHeaderProps) {
  const query = useFragment(UserProfileHeader_QueryFragment, props.query)
  return (
    <>
      <Avatar user={query.viewer} />
      {query.viewer.homeTown}
      {query.viewer.registeredAt}
    </>
  )
}
```

Now, we have two UI components that require data from the root Query type, `FriendList` and
`UserProfileHeader` .

## Compose All Query Fragments into a Single Query Operation

Finally, we can spread all our fragments into a single GraphQL Query operation that fetches all the
data on our route component. This allows us to efficiently fetch all the data required for the route
in one server roundtrip.

```jsx
import { graphql, useFragment, FragmentType } from './gql'
import { UserProfileHeader } from './user-profile-header'
import { FriendList } from './friend-list'

const UserProfileRoute_Query = graphql(/* GraphQL */ `
  query UserProfile_Query {
    ...UserProfileHeader_QueryFragment
    ...FriendList_QueryFragment
  }
`)

export function UserProfileRoute_Query() {
  const { data, loading, error } = useQuery(UserProfile_Query)
  if (loading) return <Loading />
  if (error) return <Error />
  return (
    <>
      <FriendList query={data} />
      <UserProfileHeader query={data} />
    </>
  )
}
```

Finally, we have the following component tree as mentioned before.

![Fragmentized Component Tree](https://the-guild.dev/blog-assets/unleash-the-power-of-fragments-with-graphql-codegen/component-tree-diagram.png)

## Conclusion

This pattern allows you to re-use and scale your components to the maximum while having a clear and
sane data dependency flow from the top route down to the ends of the UI component trees.

You can start adopting this pattern within your existing GraphQL application with any GraphQL client
that supports `TypedDocumentNode` through our new and battle-tested GraphQL Code Generator preset
[client-preset](https://graphql-code-generator.com/plugins/presets/client-preset).

All the following clients are supported with **ANY** framework (React, Vue, Angular, etc.)

*   urql
*   Apollo Client
*   "Vanilla" Node.js
*   graphql-request
    ([we recently added support](https://github.com/prisma-labs/graphql-request/pull/350))

You can start quickly without much configuration by following
[the preset documentation](https://graphql-code-generator.com/plugins/presets/client-preset).

<Callout type="info">
  If you want to learn how to optimize the generated code for your frontend applications, check out
  our article [Optimize your Bundle Size with SWC and GraphQL
  Codegen](/blog/optimize-bundle-size-with-swc-and-graphql-codegen).
</Callout>

Maybe this even inspires you to dig deeper into the optimizations the Relay client runtime does on
top of these patterns and convince you to use Relay within your next project. You can learn more
about those on [relay.dev](https://relay.dev).

We, as The Guild, want you to find the right tool for your expertise and the application you are
building. The most important thing is the patterns that are applicable anywhere!

## Not Having Enough?

I also talked about this topic recently at the GraphQL Berlin meetup!

<Video src="https://youtube.com/embed/YLytItSZCII" title="Are you using GraphQL the intended way?" />