Codementor Events

GraphQL Fragments are the Best Match for UI Components

Published Apr 10, 2018Last updated Oct 07, 2018
GraphQL Fragments are the Best Match for UI Components

What are GraphQL fragments? What are UI components? Why are they a match?

To build anything complicated, the one and only truly helpful strategy is to split what needs to be built into smaller parts and then focus on one part at a time.

Ideally, those parts should be designed in a way that does not couple them with each other. They should be testable on their own and they should be reusable. The big complicated system should be the result of putting these parts together and having them communicate with each other to form features.

With User Interfaces, one strategy to do this splitting is by using UI components.

What exactly are UI components?

The word component can mean different things to different people. In the domain of User Interfaces, a component can be an abstract input text box or Twitter’s full tweet form. You can pick any part of an application and call that a component. They can be small or big. They can be functional on their own or they can just be parts that have to be put together to make something functional.

Bigger components can also be composed from smaller ones. The Twitter’s TweetForm component can be composed from a TextArea component with a TweetButton component, and a few other components to attach an image, add a location, and count the number of characters typed in the text area.

All HTML elements can be considered simple components. They all have properties and behavior but they are limited in the fact that they cannot represent dynamic data. The story of UI components gets interesting when we can make a component represent data. We can do that with modern libraries and frameworks.

Data Components

We can use many modern JavaScript libraries like React.js, Angular.js and Polymer.js to define UI components that represent data. These components can then be reused for any data that matches the shape they have been designed to work with. The components do not really care about what that data is. They are only concerned about the shape of that data.

The idea of rich components is actually coming natively to the browser with what is commonly labeled as Web Components. Many browsers already support most of the features needed to define and use Web Components. The Polymer.js project is designed to first provide polyfills to support using Web Components in any browser and then enhance their features.

Let’s assume we want to build an app like Twitter by using rich data components. I will take one example page from that app and analyze it in terms of components and their data requirements.

Let’s talk about the user’s profile page. It’s a simple page that displays public information about the user, some stats, and the list of their tweets.

For example, if you navigate to https://twitter.com/manifoldco on Twitter, you will see something like:


The @manifoldco profile page at Twitter

I can see at least 15 components on this page:

  • The Header component, which includes the following list of components: ProfileImage, BackgroundImage, TweetCount, FollowingCount, FollowersCount, LikesCount, and ListsCount
  • The Sidebar component, which includes the following list of components: UserInfo, FollowersYouKnow, User_Media,_ and MediaItem.
  • The TweetList component, which is a list of Tweet components.

Of course, this is just my choice of components. This page can be built with a lot more components and it can also be built with just two components. No matter how small or big the components that you design are, they will share a simple fact: they all depend on some shape of data.

For example, the Header component above needed a data object to represent a profile. The shape of that data object might look like:

const profileData = {
  profileImageUrl: ...,
  backgroundImageUrl: ...,
  tweetsCount: ...,
  followingCount: ...,
  followersCount: ...,
  likesCount: ...,
  listsCount: ...,
};

The TweetList component above needed a data object that might look like:

const tweetList = [
  { id: ...,
    userName: ..,
    userHandle: ...,
    date: ...,
    body: ...,
    repliesCount: ...,
    tweetsCount: ...,
    likes: ...,
  },
  { id: ...,
    userName: ..,
    userHandle: ...,
    date: ...,
    body: ...,
    repliesCount: ...,
    tweetsCount: ...,
    likesCount: ...,
  },
  ...,
];

These components can be used to render information about any profile and any list of tweets. The same TweetList component can be used on Twitter’s main page, a list page, or the search page.

As long as we feed these components the exact shape of data that they need, they will just work. This is where GraphQL comes into the picture because we can use it to describe the shape of data needed by an application.

To simplify the Twitter example, I will consider that we are going to build the profile page with just these main four components: Header, Sidebar, TweetList, and Tweet.

Declaring Data Requirements

GraphQL is all about declaring data requirements. The whole language is designed for that purpose. We can write a single GraphQL query to ask for the data needed for an application.

Every application has data requirements. The data required by an application is the sum of the data required by that application’s individual components.

The cool thing about the GraphQL language is that it offers a way to split a big query into smaller ones. Just like we split an application into multiple components, we can split a GraphQL query into multiple fragments.

See the next section for an introduction to GraphQL fragments.

Do you see where I am going?

  • We have an application that is composed using components.
  • Every component has some data requirements.
  • We need a GraphQL query to declare all of that application’s data requirements.
  • We can split a GraphQL query into fragments.

This makes a GraphQL fragment the perfect match for a component! We can simply use a GraphQL fragment to represent the data requirements for a single component and then put these fragments together to compose the data requirements for the whole application.

Let’s do that for our example, but let me first make sure you know about the awesome power of GraphQL and its fragments.

What exactly are GraphQL fragments?

To ask a GraphQL server for data, we send it a query. A query is simply a nested set of fields.

Let me use GitHub for some examples. You can test all GitHub query examples below using GitHub’s GraphQL API Explorer.

Here is a GraphQL query you can use at GitHub to read some information about a user:

{
  viewer {
    login company avatarUrl
  }
}

The above query reads information about the currently authenticated user (hence the root field, viewer). However, you can use a similar query to ask for the same information for any user:

{
  user(login: "ntassone") {
    login company avatarUrl
  }
}

You can also use a similar query to ask for the same information for every user who made a commit to a repository, created an issue in a repository, or just star-gazed a repository. Here’s a query to list the star-gazers of a repository:

{
  repository(owner: "manifoldco", name: "torus-cli") {
    stargazers(first: 100) {
      nodes {
        login company avatarUrl
      }
    }
  }
}

If you notice, all three query examples below share something: the three bolded fields. Now imagine that we want to ask the server for data using all three queries at the same time (for some abstract reason).

The bolded fields would have to be repeated:

If we later decided to add another field to the list of these three common user fields, we would need to modify multiple places in the big query. This is where a GraphQL fragment can be useful. We can use them to avoid this repetition.

We first define a GraphQL fragment that represents the fields that are common between multiple queries. A GraphQL fragment has to be defined on a data type. In our examples, that data type would be a GitHub User:

fragment UserInfo on User {
  login company avatarUrl
}

Now, everywhere we need to read these exact three fields for a user, we can use the UserInfo fragment instead of repeating them. To use a fragment, we spread it at the level where it is needed using the three-dots spread operator:

...UserInfo

The abstract query that combines all three examples above becomes:

{
  viewer {
    ...UserInfo
  }
  user(login: "ntassone") {
    ...UserInfo
  }
  repository(owner: "manifoldco", name: "torus-cli") {
    stargazers(first: 100) {
      nodes {
        ...UserInfo
      }
    }
  }
}

Fragments can be defined on any type and we can nest them as well. Here is a bigger fragment that uses the smaller UserInfo fragment:

fragment StarGazersInfo on Repository {
  stargazers(first: 100) {
    nodes {
      ...UserInfo
    }
  }
}

Which means, the abstract query example can now be:

{
  viewer {
    ...UserInfo
  }
  user(login: "ntassone") {
    ...UserInfo
  }
  repository(owner: "manifoldco", name: "torus-cli") {
    ...StarGazersInfo
  }
}

In summary, GraphQL fragments allow us to:

  • Reuse common parts of a GraphQL query
  • Compose a GraphQL query by putting together multiple fragments

You can simply think of GraphQL fragments as the components of GraphQL queries.

Using GraphQL fragments for the Twitter example

Let’s come up with the data required by the Twitter’s profile page example above using a single GraphQL query for each component we identified.

The data required by the Header component can be declared using this GraphQL fragment:

fragment HeaderData on User {
  profileImageUrl
  backgroundImageUrl
  tweetsCount
  followingCount
  followersCount
  likesCount
  listsCount
}

The data required by the Sidebar component can be declared using:

fragment SidebarData on User {
  userName
  handle
  bio
  location
  url
  createdAt
  followersYouKnow {
    profileImageUrl
  }
  media: {
    mediaUrl
  }
}

Note that the followersYouKnow part and media part can also come from the sub-components that we identified earlier in the Sidebar component.

The data required by a single Tweet component can be declared using:

fragment TweetData on Tweet {
  user {
    userName
    handle
  }
  createAd
  body
  repliesCount
  retweetsCount
  likesCount
}

Finally, the data required by the TweetList component is an array of the exact data required by a single Tweet component. So we can use the Tweet fragment here:

fragment TweetListData on TweetList {
  tweets: {
    ...TweetData
  }
}

To come up with the data required by the whole page, all we need to do is put these fragments together and form one GraphQL query:

query ProfilePageData {
  user(handle: "manifoldco") {
    ...HeaderData
    ...SidebarData
    ...TweetListData
  }
}

Now we can send this single ProfilePageData query to the GraphQL server and get back all the data needed for all the components on the page.

When the data comes back, we can identify which component requested which parts of the response and make those parts available to only the components that requested them. This helps isolating a component from any data that it does not need.

But this is not the coolest thing about this approach. By making every component responsible for declaring the data it needed, these components will have the power to change their data requirements when necessary without having to depend on any of their parent components in the tree.

For example, let’s assume Twitter decided to show the number of views each tweet has received next to the likesCount. All we need to do to satisfy this new data requirement is to modify the TweetData fragment:

fragment TweetData on Tweet {
  user {
    userName
    handle
  }
  createAd
  body
  repliesCount
  retweetsCount
  likesCount
  viewsCount
}

None of the other components in the application need to worry about this change or even be aware of it. For example, the direct parent of a Tweet component, the TweetList component, does not need to be modified to make this change happen. That component always constructs its own data requirements by using the Tweet component’s data requirement no matter what that Tweet component asked for.

This is simply great. It makes maintaining and extending this app a much easier task.

Conclusion

Using rich data UI components to build big applications is a winning strategy. If you are using frameworks like Angular.js or React.js, you are already doing that.

All data components require some shape of data. You can use GraphQL to declare that shape. GraphQL queries can be composed using fragments. Fragments are to queries what UI components are to a full application. By matching every UI component in the application to a GraphQL fragment, we give these components the power of independency. Each component can declare its own data requirement using a GraphQL fragment and we can compose the data required by the full application by just putting these GraphQL fragments together.

That’s all I have for this topic. Follow me for more articles on GraphQL and its ecosystem. Thanks for reading!


Learning React or Node? Checkout my books:

Discover and read more posts from Samer Buna
get started
post commentsBe the first to share your opinion
Show more replies