Relay 101: Building A Hacker News Client
Without Relay, you have to download, transform, and cache each slice of your server data by hand. Tools like Flux and Redux help prevent some bugs that occur in this process, but still leave considerable room for human error in apps with lots of data flowing to and from the server. Relay removes most of the common boilerplate and enables app engineers to concisely and safely retrieve the data they want.
Once upon a time, Rails showed how to make a blog in 15 minutes. In that tradition, we’re going to make a Hacker News client with Relay. This assumes you are familiar with Node, NPM, and React, but nothing more.
Currently Relay requires that your server expose a GraphQL endpoint. GraphQL is very neat, but unless you work at Facebook you probably don’t such an endpoint handy.
Instead of making our own, we’re going to use GraphQLHub’s GraphQL endpoint. GraphQLHub is a burgeoning repository of GraphQL translations of existing APIs, such as the APIs for Hacker News and Reddit — and hey, I also happen to maintain it :)
This little tutorial will bring you up to speed on basic GraphQL syntax, and you definitely don’t need to read the specification before starting. If you’re curious about writing a GraphQL endpoint from scratch, check out Your First GraphQL Server.
Setting Up The Project
We’re intentionally breezing through a lot of the Webpack and Babel setup, since we’re here to talk about Relay — feel free to drop a Note on any part if you want more information.
Let’s start by making a new Node project:
This will create a package.json file in your directory with some pre-populated information. Time to start installing some packages:
Webpack looks for a configuration file called “webpack.config.js”, so we should make that in the same directory:
And then paste this into it:
You’ll notice that we’re referring to an index.js file in there. Go ahead and create that with something really simple:
As we continue, all of our app’s code will go into that file, so keep it handy in your editor.
We’ve still got a few more steps — in the package.json file, add a “start” entry to your scripts:
What this does is allow us to type “npm start” and have it run the Webpack development server we installed earlier. Give it a shot and leave it running in its own terminal session/tab:
Open http://localhost:8080/webpack-dev-server and see — a list of files! Which is good, but what we need is some HTML for our app. Back in your project folder, create index.html and fill it with some content:
Refresh the dev server, and see the expected pop-up:
Now the fun can start.
Building A Static Component
Our little app is going to mimic the Hacker News front page, and our first UI component is what each individual post will look like. To start creating the component, we need to install the React and React-DOM packages:
Note that we have some very specific version requirements (if you’re in the future and this has changed, drop a Note!). Back in index.js, remove our old alert and start by defining an Item component:
Note that all of our data come from this “store” prop — we’ll see why in a moment, but go with it for now.
Let’s get something on the screen — render a dummy Item like so:
Refresh your dev server and you should see something like this:
Data From The Server
Time to add some Relay. Instead of using a static item, we’re going to fetch the item by its ID from GraphQLHub. Start by installing some Relay packages:
Why did we install more than just react-relay? Well, the current implementation of Relay is going to require us to do a bit more setup — specifically, we need to connect this “babel-relay-plugin” to Babel. The plugin will talk to the GraphQLHub endpoint and generate some more configuration for Relay.
To connect the plugin, open up webpack.config.js and edit the “query” option:
This tells Babel to look for a plugin file called babelRelayPlugin.js. Create that file and copy-paste the boilerplate:
Cool — now kill your “npm start” process and restart it. Now every time your app re-bundles, it will query the GraphQLHub server (using GraphQL’s super neat introspection API) and prepare our Relay code.
Back in index.js, time to finally import Relay:
What now? We’re going to wrap our Item component with a higher-order component. This new React component will be created and managed by Relay, which is where the magic happens:
Boom, that happened. In plain-english, this is what’s happening:
Hey Relay, I’m going to re-define my Item component as a new component which wraps the original in a container.
For the component’s “store” prop, I need the data described in this GraphQL fragment.
I know I need it on a “HackerNewsAPI” object because I explored the API via http://GraphQLHub.com/playground/hn.
Note that we only describe a GraphQL fragment (fragments are analogous to aliases/symlinks in a query), not the final query for how to fetch all the data. This is one of Relay’s strengths — an individual component declares exactly what data it needs, not how to retrieve it.
But at some point we do need a finalized GraphQL query, which is where Relay Routes come into play. Relay.Route has nothing to do with browser history or URLs — instead, it has to do with creating a “root query,” which bootstraps our data requests.
So, let’s make a Relay Route. Add this below our new Item definition:
Note that our GraphQL now begins with the root query. Relay allows injection of fragments via ES6 string interpolation, which is how components share (but do not duplicate) their data requirements to their parent components.
Time to get something on the screen! Change our old rendering code to this:
The Relay RootContainer is the top-level component which kicks off a query with a component hierarchy. We do a bit of networking setup, and then render the new component into the DOM. You should see this in your browser:
A List Of Components
We have something starting to resemble the front page of Hacker News. Instead of hard-coding one item, we need to show a list of the top items. In the Relay world, this pattern generally requires us to create a new list component, which embeds many individual item components (each requesting their specific data).
In code, start by creating a new TopItems component:
We could go through the same “create mock data” exercise as earlier, but instead we’re going to skip straight to wrapping TopItems with Relay:
Now instead of requesting one item, we request the “topStories”. For each story, GraphQL will request the data from the Item’s fragment, so we’ll get only the data we need.
But hang on — currently our Item fragment requests a specific item (#8863). We need to update our query to only be a fragment on individual HackerNewsItem objects:
And since we’re no longer requesting an item in our fragment, we need to change how the prop access works in the render function:
One last tweak — we need to change the Relay RootContainer to use our new TopItems component:
Voila! Check your app in the browser:
Variables in Queries
So now we have the basic knowledge to start building Relay apps, but I want to show off another Relay feature: variables.
In most apps, queries aren’t static and we often need to request different data at runtime. One way Relay allows us to accomplish this is injecting variables in our GraphQL queries. For our little app, we’re going to add a switch to change which types of stories we fetch (the top, or the newest, etc).
To start, we need to change our TopItems query:
The dollar-sign-prefixed “storyType” denotes a GraphQL variable (note that this isn’t an ES6 string interpolation). We give it an initial value of “top” via the initialVariables configuration, which lets our component render immediately.
That’s the only Relay-level change we need to make, which is pretty sweet. We haven’t changed anything related to how an individual component renders or requests data — that process is totally decoupled.
Now we need to edit our TopItems component rendering to account for switching story types. Update the render method to look like this:
Some new stuff going on here! We’re now accessing the “relay” prop, which has some special properties. Any component created with Relay has this prop injected — if we wanted to just unit test our TopItems component, we could inject a mock object ourselves.
Aside from the Relay variables, everything else is vanilla React — we create a new select element, give it an initial value, and get ready to respond when it changes. When that change happens, we need to tell Relay to use a new variable value. This looks like:
It’s that simple — Relay will detect what part of the query has changed and re-fetch as needed. We also set the local component state, which makes it feel a bit snappier.
Refresh your browser and you should be able to switch between stories with ease. If you switch between story types, you’ll notice that Relay won’t request new data if you’ve already loaded that particular feed.
So, that’s a whirlwind introduction to Relay. We haven’t even touched on mutations (which is how you write data back to the server) or how to handle a loading spinner while data fetches. Relay is very flexible, but comes at the price of some configuration and more reading on our part.
Relay may not be right for every app or team, but it’s a very intriguing take on a common problem that might help some lightbulbs go off.