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
}
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
}
}
This would be the equivalent of:
query GetUser {
user {
id
name
age
birthday
}
}
The Power of Fragments In Code Organization
Consider the following view:
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
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>
)
}
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>
)
}
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>
)
}
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!
Top comments (0)