DEV Community

Cover image for Using GraphQL Fragments to Better Organize Your Code
Andrew Gionfriddo for BetterHealthcare

Posted on

Using GraphQL Fragments to Better Organize Your Code

At BetterHealthcare, many of the views we build have a rich amount of information broken down into smaller components throughout the page. In order to organize the information, we have started using GraphQL fragments. By organizing our code with a one component to one fragment relationship, we've been able to reduce network requests, improve code organization, and increase the reusability of our networking code.

What is a GraphQL Fragment?

A GraphQL Fragment is a chunk of reusable GraphQL code that can be used throughout a codebase. They allow for reusability and composability across your mutations and queries. They look like the following:

fragment UserFields on User {
    name
    age
    birthday
}
Enter fullscreen mode Exit fullscreen mode

In this example, the name of the fragment us UserFields. When writing a fragment we have to say what GraphQL data type this fragment belongs to. In this case, it belongs on User. We can then define our fields that we want on our fragment the same we we would define fields in a query or in a mutation.

We can then use this fragment in our queries like so:

query GetUser {
    user {
        id
        ...UserFields
    }
}
Enter fullscreen mode Exit fullscreen mode

This would be the equivalent of:

query GetUser {
    user {
        id
        name
        age
        birthday
    }
}
Enter fullscreen mode Exit fullscreen mode

The Power of Fragments In Code Organization

Consider the following view:

Profile Page

We want to use one query for this page, but we don't want to have a query that's large and unwieldy. Similarly, we don't want to write a query for every component, because then we would have four round trip network requests. By using fragments, we can break down the query information and disperse it throughout the page's components while still only performing one network request.

We'll set up our file structure like the following:

src/
|-- Profile/
|---- ProfilePage.js
|---- ProfileUserInfo.js
|---- ProfileAccountPreferences.js
|---- ProfileRolesPermissions.js
|---- ProfileLoginContact.js
|---- ProfileWrapper.js

Enter fullscreen mode Exit fullscreen mode

Within each component we'll set up a fragment that will be responsible for fetching the data that each of the components need to render. Let's look at a simplified version of the ProfileUserInfo.js as an example. We will colocate the fragment with the component that uses the fragment.

// ProfileUserInfo.js

import gql from 'graphql-tag'

export const USER_INFO_FRAGMENT = gql`
    fragment UserInfo on Profile {
        firstName
        lastName
        gender
    }
`

export default function ProfileUserInfo({ userInfo }) {
    return (
        <Card>
            <TextInput label="First Name" value={userInfo.firstName} />
            <TextInput label="Last Name" value={userInfo.lastName} />
            <SelectInput label="Gender" value={userInfo.gender}>
                <option>Male</option>
                <option>Female</option>
            </SelectInput>
        </Card>
    )
}

Enter fullscreen mode Exit fullscreen mode

Pulling It All Together

Once we set up all of our components and fragments like above, we can then import them to one central query on parent component. In this case our parent component is ProfilePage.js. We can use a template string to insert the fragments into this query so we can then use it. All this code will result in just one network request despite the logic being broken up across several files.

// ProfilePage.js

import gql from 'graphql-tag'
import { useQuery } from '@apollo/client'
import ProfileUserInfo, { USER_INFO_FRAGMENT } from './ProfileUserInfo'
import ProfileAccountPreferences, { ACCOUNT_PREF_FRAGMENT } from './ProfileAccountPreferences'
import ProfileRolesPermissions, { ROLES_AND_PERMISSIONS_FRAGMENT } from './ProfileRolesPermissions'
import ProfileLoginContact, { LOGIN_CONTACT_FRAGMENT } from './ProfileLoginContact'
import PageWrapper from './PageWrapper'

const PROFILE_QUERY = gql`
    query Profile {
        profile {
            id
            ...UserInfo
            ...AccountPreferences
            ...RolesPermissions
            ...LoginContact
        }
    }
    ${USER_INFO_FRAGMENT}
    ${ACCOUNT_PREF_FRAGMENT}
    ${ROLES_AND_PERMISSIONS_FRAGMENT}
    ${LOGIN_CONTACT_FRAGMENT}
`

export default function ProfilePage() {

    const { data, loading, error } = useQuery(PROFILE_QUERY)

    if (loading) return <Loader />;

    return (
        <PageWrapper>
            {error && <ErrorMessage error={error} />}
            <ProfileUserInfo userInfo={data.profile} />
            <ProfileContactInfo contactInfo={data.profile} />
            <ProfileRolesAndPermissions rolesAndPermissionInfo={data.profile} />
            <ProfileAppointments appointmentsInfo={data.profile} />
        </PageWrapper>
    )
}
Enter fullscreen mode Exit fullscreen mode

Reusability

Not only can we use our fragments in our parent, component, but we can use them in our mutations in our child components. As you can see from the screenshot of the view we're putting together, each component is responsible for updating its own information. We can use the fragments we wrote in our mutations. Lets pull up our ProfileUserInfo component again. This time we're going to build onto it. This version of the component will allow us to update the information by calling a mutation that makes use of our UserInfo fragment.

// ProfileUserInfo.js

import gql from 'graphql-tag'
import { useState } from 'react';
import { useMutation } from '@apollo/client'

export const USER_INFO_FRAGMENT = gql`
    fragment UserInfo on Profile {
        firstName
        lastName
        gender
    }
`

const UPDATE_USER_INFO = gql`
    mutation UpdateUserInfo($firstName: String, $lastName: String, $gender: Gender) {
        updateUserInfo(input: { firstName: $firstName, lastName: $lastName, gender: $gender }) {
            id
            ...UserInfo
        }
    }
    ${USER_INFO_FRAGMENT}
`

export default function ProfileUserInfo({ userInfo }) {
    const [firstName, setFirstName] = useState(userInfo.firstName);
    const [lastName, setLastName] = useState(userInfo.lastName);
    const [gender, setGender] = useState(userInfo.gender);

    const [updateUser] = useMutation(UPDATE_USER_INFO, {
        variables: {
            firstName,
            lastName,
            gender
        }
    })

    return (
        <Card>
            <form onSubmit={updateUser}>
                <button type="submit">Submit</button>
                <TextInput 
                    label="First Name" 
                    value={firstName} 
                    onChange={(e) => setFirstName(e.currentTarget.value)} 
                />
                <TextInput 
                    label="Last Name" 
                    value={lastName} 
                    onChange={(e) => setLastName(e.currentTarget.value)} 

                />
                <SelectInput 
                    label="Gender" 
                    value={gender} 
                    onChange={(e) => setGender(e.currentTarget.value)}
                >
                    <option>Male</option>
                    <option>Female</option>
                </SelectInput>
            </form>
        </Card>
    )
}

Enter fullscreen mode Exit fullscreen mode

As you can see, we're now using this fragment in two places, the query that fetches the data, and the mutation that updates the data. You can imagine that if we had to use this chunk of information in other parts of our codebase, we could simply import it. The benefits would become even more apparent if this fragment was larger than just three fields.

Conclusion

Fragments give us a bunch of benefits:

  • Cleaner Code: We don't have one giant query sitting on our parent component, but rather the fields are located with the exact component that uses them.
  • Separation of concerns: The parent component doesn't have to know about what's going on in its children's components. All it has to do is create the query based on the fragments that the children give it.
  • Reusability. We can use the same fragments in multiple queries and mutations across our codebase to reduce code duplication.
  • Fewer network requests: By using fragments, we get the benefit of code separation without having to write a query (and thus a new network request) for every single component on the page.

When we first started writing GraphQL at BetterHealthcare, we struggled with out to properly organize our queries. We didn't want to do too many network requests, but we also didn't want to write massive queries for each page. Fragments provide the benefits of breaking up the logic of your queries without performing extra network requests.

Fragments also give you even more benefits when using Graphql Codegen and Typescript. We will explore this in a future post. I hope you learned something about the power of GraphQL fragments from this post!

Discussion (0)