tl;dr: Use
relay --validate
to catch Relay validation errors in CI!
I'm a big fan of GraphQL (seriously, ask me about GraphQL). One tool I've been absolutely loving lately has been Relay. We use Relay at Clubhouse to build some really awesome features in Clubhouse for iOS. As of the writing of this blog post, we've got nearly 100 different components using Relay in some way, shape or form!
#Hold up, what's Relay again?
Relay is to GraphQL and data fetching what React is to DOM and UI. It's a declarative way to dictate what data your components need, rather than how and when to fetch it.
A typical use-case of Relay looks something like this. Say you've got an social media app that shows a list of friends for the current user.
Each of these components gets data from a different part of our tree. We can
colocate our queries of how to fetch the data for each component using
Relay. A common way to do this is using Relay's createFragmentContainer
(there's other methods to do this too, to handle pagination, refetching, etc. ,
but for the sake brevity in this post, let's focus on fragment containers).
Some of the components may look like this:
// in src/Avatar.js
const Avatar = ({ user }) => <Image src={user.thumbnailImgSrc} />;
const AvatarContainer = createFragmentContainer(
Avatar,
graphql`
fragment AvatarContainer_user on User {
thumbnailImgSrc
}
`,
);
// in src/FriendListItem.js
const FriendListItem = ({ user }) => (
<View>
<Avatar user={user.thumbnailImgSrc} />
<Text>{user.name}</Text>
</View>
);
const FriendListItemContainer = createFragmentContainer(
FriendListItem,
graphql`
fragment FriendListItemContainer_user on User {
name
...AvatarContainer_user
}
`,
);
// in src/FriendsList.js
const FriendsList = ({ currentUser }) => (
<FlatList
data={currentUser.friends}
renderItem={({ item }) => <FriendListItem user={item} />}
/>
);
const FriendListItemContainer = createFragmentContainer(
FriendListItem,
graphql`
fragment FriendListItemContainer_currentUser on CurrentUser {
friends {
...FriendListItemContainer_user
}
}
`,
);
Now each of our components composes together both is UI components and it's data-requirements, declaratively!
#Dope. Relay seems cool. But what's this __generated__
directory I've got here?
The Relay Compiler will read through our source code to find Relay components and their colocated GraphQL queries to generate artifacts that will be used by the Relay runtime.
These artifacts look something like this (abbreviated here):
// from src/__generated__/AvatarContainer_user.graphql.js
const node /*: ReaderFragment*/ = {
kind: "Fragment",
name: "AvatarContainer_user",
type: "User",
metadata: null,
argumentDefinitions: [],
selections: [
{
kind: "ScalarField",
alias: null,
name: "thumbnailImgSrc",
args: null,
storageKey: null,
},
],
};
(node/*: any*/).hash = '693ff4889bc9965ae9f6512d628b7292'; // prettier-ignore
module.exports = node;
We can see that for the Avatar
component, the compiler generates a static set
of data for the GraphQL fragment it needs to fetch the data.
Relay recommends checking in these artifacts from the Relay Compiler into your source control, as they're crucial and necessary to run your app.
As you work through your app, you'll likely run yarn relay --watch
to run the
compiler in watch mode to automatically generate these artifacts and as you
build new components, pages, features and so on. You'll see that as your change
your GraphQL queries, the Relay Compiler will indicate what is changed, and you
can see the changes in your git diff
as well.
❯ yarn relay
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --watch
Writing js
Updated:
- AvatarContainer_user.graphql.js
Unchanged: 2 files
❯ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: src/Avatar.js
modified: src/__generated__/AvatarContainer_user.graphql.js
However, these artifacts are only autogenerated if you run the Relay Compiler
while you're working! If a team member (or even yourself) forgets to run
yarn relay
while you're working, and a GraphQL query changes, your generated
artifacts will be out of sync with your product code! 😰
🤔 So how do we fix this? How do we prevent our product code's queries from coming out of sync with our autogenerated Relay artifacts?
Luckily, Relay makes this easy.
The Relay Compiler includes
a flag called --validate
.
This flag will run the Relay Compiler and if there are any autogenerated
artifacts that will be overwritten based on the GraphQL queries, the compiler
will indicate that an artifact is our of date and exit with an error.
❯ yarn relay --validate
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --validate
Writing js
Out of date:
- AvatarContainer_user.graphql.js
error Command failed with exit code 101.
This makes it really easy to validate your Relay codebase in CI, just as you
might run your tests in CI! Add relay --validate
to your CI flow today and
catch changes in GraphQL queries before they land on master.