The days that I had a hard time maintaining codebases with Apollo GraphQL are behind me. It all changed when I adopted a single concept.
Apollo Client (a.k.a. Apollo GraphQL) is a GraphQL client used for consuming GraphQL data in web and mobile apps. It has caching, tracks network status, and does a fair amount of heavy lifting so developers can focus on building the product.
As the app grows and becomes more complex, it can get hard to know where the data is coming from and what piece of UI needs that data. What can be done to solve this is colocating components and fragments.
Declaring the fragment
Let's say that we have a component that it's responsible for displaying a card with information about a user:
// notice the component name, it will be used in the fragment
export const UserCard = ({ user }) => {
return (
<div>
<h1>{user.name}</h1>
<div>
<img src={user.profile_picture?.uri} />
</div>
</div>
);
};
// attach a fragments object to your component, more on that later
UserCard.fragments = {
// notice that this is the name of the GraphQL type, using camel-case
// the fragment name follows this pattern to avoid conflicts: <Component Name>_<GraphQL Type>
// so in this case, the fragment name is UserCard_User
user: gql`
fragment UserCard_User on User {
name
profile_picture {
uri
}
}
`,
};
Using the fragment
Now, we want to use this card on a page that we're building:
import { UserCard } from '../components/UserCard';
const QUERY = gql`
query UserPage {
user(id: 200) {
id
# spread the fragment here, so it's included in the query
...UserCard_User
}
}
${UserCard.fragments.user}
`;
const UserPage = () => {
const { data } = useQuery(QUERY);
return (
<div>
<h1>Some nice title</h1>
{/* pass the user data to the component */}
<UserCard user={data.user} />
</div>
);
};
And that's all! The data declared in the UserCard
will also be included in the query and the page just needs to forward it to the component.
Updating the fragment
So, let's say that after a few weeks the Product Manager comes back and says:
"Hey, we discovered that it's important to see the user last name as well. Can you add it?"
Sure thing! That's what we have to do:
export const UserCard = ({ user }) => {
return (
<div>
{/* add the last name in the UI */}
<h1>
{user.name} {user.last_name}
</h1>
<div>
<img src={user.profile_picture?.uri} />
</div>
</div>
);
};
UserCard.fragments = {
user: gql`
fragment UserCard_User on User {
name
# add the "last name" to the fragment
last_name
profile_picture {
uri
}
}
`,
};
So with just two lines of code, all the places that are using this card will be updated and have the right data. No more updating each query and passing down props. š
Bonus: TypeScript
It gets even better with TypeScript, as when the types are generated, they're also colocated with the component:
import { UserCard_User } from './__generated__/UserCard_User';
type Props = {
user: UserCard_User;
};
export const UserCard = ({ user }: Props) => {
return (
<div>
{/* add the last name in the UI */}
<h1>
{user.name} {user.last_name}
</h1>
<div>
<img src={user.profile_picture?.uri} />
</div>
</div>
);
};
UserCard.fragments = {
user: gql`
fragment UserCard_User on User {
name
# add the "last name" to the fragment
last_name
profile_picture {
uri
}
}
`,
};
Thanks for reading!
What are your thoughts on using Apollo with fragments? How would you change it?
Let's keep in touch! Here's my Twitter.
References
https://www.apollographql.com/docs/react/data/fragments/
https://relay.dev/docs/guided-tour/rendering/fragments
https://kentcdodds.com/blog/colocation
Top comments (1)
What an awesome approach!