Sign inSign up

GraphQL & React tutorial (part 5/6)

Combining components into screens and mocking our server

loading
Tom Coleman
@tmeasday
Last updated:
GraphQL & React tutorial is a series of posts following the development process of an “Inbox Zero” style todo list application, built in JavaScript with the most innovative and production ready tools as of early 2017. Parts: 1, 2, 3, 4, 5, 6.

So far in this series we’ve concentrated on the smallest possible unit of UI, and built from the component up. Doing so has allowed us to develop each component in isolation, figure out its data needs, and play with it in a component explorer without needing to stand up a server or build out screens.

In this post we will start combining components together and fetching data for them. We’ll build the screens of our app, but do so without requiring a server yet.

A GraphQL-powered Taskbox

We’ll focus on the inbox screen in this post, although the other screens will be similar in approach. To get started on the inbox, we’ll follow a “presentational/container” component split and build a “pure” presentational inbox and then use Apollo’s (our choice of GraphQL library) graphql higher-order component [HoC] to build the container.

The presentational component is simply a combination of other components, and we can develop it in exactly the same way as we developed our components in the first place; using React Storybook to drive different use cases.

We can start by building a set of stories for the different states a user could be in looking at the homepage:

This isn’t particularly different to how we build the task component’s stories in part 4, and again, we are more or less following the same process. We have also built stories to cope with more “imperfect” states — what happens while the data is loading, or what about if there is an error? Building a complex component inside a component explorer in this way is great because such states are often overlooked or difficult to build against in a traditional process.

Once we’ve built out those stories, we can use them to build out the component:

Again, the component isn’t too complicated, and we are careful to deal with all of the different states we’ve outlined in our stories. Running the states in storybook makes it easy to test we’ve done this correctly:

You may notice that this story generates tasks in a very similar way to the Task.story.js file in the previous part. It makes sense to refactor that logic out into a single test helper utility. In fact, as I alluded to then, it would be cool to have a mocking utility which automatically creates such a function from a GraphQL fragment!

Wire in data

The Inbox component isn’t really all that different to any other component that we’ve built so far in this process (although not very reusable given it renders quite a specific output for the inbox screen). To actually use the component we will need to provide it with data, and that’s exactly what our InboxScreen container will do.

A container is a component that renders a single presentational component, whilst doing the “impure” work for that component, such as asynchronously fetching data.

In our case, our container will be generated by Apollo’s graphql higher-order component, which does the work of sending queries to our GraphQL server and handing off the results to our component when they are ready.

First, we install the Apollo libraries we will need:

yarn add apollo-client react-apollo graphql-tag

Then we use them:

The above code attaches a query to fetch our pinned and inbox tasks, sets some polling options, and passes that data through as props to our “pure” Inbox component. Let’s look at each step in a little more detail.

The query is short but has a few interesting things going on:

  1. We name the query InboxQuery. Strictly we don’t need to do this, but it helps debugging across our stack to know which query we are looking at and where it comes from. A convention is useful here.
  2. We use the me { tasks(state: ...) } field that we set up to get the list of tasks of a given type assigned to the current user. As we are using it twice in this query (once for the pinned tasks and once for the inbox tasks), we use an alias (pinnedTasks: tasks(...) to “rename” the field.
  3. We re-use the TaskList.fragments.task fragment (which built off the Task.fragments.task fragment) so that this component doesn’t need to concern itself with which fields it’s subcomponents will need; this fragment mechanism allows some data-fetching concerns to stay at the component level.

In the options argument we set the query to always fetch (so it doesn’t hit Apollo’s cache if we come back to this screen), and to poll every ten seconds.

Finally the props function allows us to control the data that is passed to the Inbox component. The argument to the props function is the default prop set by the graphql container (to put everything on a single data prop) and the return value of the props function is what will actually get passed. So we can use this function to retain control over the shape of the props of our Inbox component, instead of having that dictated by the particular library we are using.

Add a mutation

Apart from the two lists of tasks, we also need to provide our other arguments to the page, which allow the user to move tasks between lists. To do so, they’ll need to call a GraphQL mutation on the server. We can attach a mutation as a callback to a component using the the graphql HoC also:

We attach the mutation similarly, by creating a withOnSnoozeTask HoC using the graphql function. The mutation takes a single variable taskId. Our props function takes the “raw” mutate callback provided by graphql and renames it onSnoozeTask as well as making it take a single String argument rather than an object of options (which is what the mutate function expects). This means that the Inbox will get passed a prop called onSnoozeTask, as it expects.

Notice as well that we re-use the TaskList.fragments.task fragment in the mutation query so that when the mutation returns from the server we get all the same fields of the changed task. This is important because it means we’ll always have all the correct fields in Apollo’s document store for tasks; in the future we’ll use this to dynamically and optimistically change the task list with the mutation results; for now we just use the refetchQueries option to ensure that we re-query all the data again whenever we make a change (we’ll work on making this better in our next post once we have a server to dynamically change data on).

Test the GraphQL container

As our container component expects to communicate with an external GraphQL server, it is more difficult to test than our simple “props to HTML” presentational components. However we can still test it within our component explorer by using a mocked Apollo client.

We need to use a few tools to do this:

yarn add --dev graphql graphql-tools apollo-test-utils

Then we can follow Jonas Helfer’s technique to build a testing network interface which returns mocked data based on our schema.

We start by adding our schema (which we developed in part 3) to the frontend app in the file src/schema.js. We need to wrap the raw “Schema Language” of this file to turn it into JS:

Now we will build a custom network interface for Apollo that uses the types in that schema to automatically build test data. We do want to control one piece of data — we want the output of the tasks resolver on me to return a fixed set of tasks for each of our stories.

So we write:

We use the mockedTasks variable to act as our mocked “server”. Each test initializes the state of the server and then the client will query the server by calling the resolvers that we have provided.

In many ways these stories closely parallel the stories we wrote for Inbox above, which were simpler because we could specify the inboxTasks and pinnedTasks props directly. Given that InboxScreen is simply a code-only (with no extra UI) wrapper for the Inbox, we could convert these stories into purely automated tests and not access them in Storybook. In that case, we would simply check that for the given mocked response, our contained Inbox component gets the props we expect at the times we expect. We’ll consider such a test case in the next part of this series.

Testing Mutations

One interesting thing we can do with the above approach is provide a resolver for the updateTask mutation. When we test the story above, we can click the snooze button and see the "updateTask" action logged in React Storybook.

We could take this further and actually update our mockedTasks “server” when we run the mutation. This seems a little like overkill at this stage, but could be a good candidate for automated tests, to ensure that more complicated mutation behaviours (such as query reducers/updaters and optimistic UI) work correctly.

Build the app using React Router v4

Now we’ve built a screen of our app and wired it up with data, we are at the point where we can put the entire frontend app together.

Our app is web-based so we’ll associate each screen of the application with a URL path — the screen we’ve been building (the inbox) at the root path (/), and the other lists at corresponding URLs, i.e./snoozed and /archived. To achieve this, we’ll use the defacto URL router for React, React Router, and in particular it’s v4 API (which is in beta as of this writing).

To install the router, we can run

yarn add react-router-dom@next

Using the router is actually quite simple, we just layout our main page layout, and use the Route component to choose which component to display in each position within that layout. In our case, we have a simple fixed menu on the left and then our different task listing screens on the right. So our App component looks like:

Each of the “Screen” components is responsible for fetching its own data, and we will render the MenuScreen as well as one of the InboxScreen, SnoozedScreen, ArchivedScreen depending on which URL we are at.

Our App component above expects to be rendered in a specific context (a context in React is like a set of “hidden” props that are available all the way down the tree; it allows things like the Match component above to read route data from the Router component without having to wire everything together). In our case, it expects route information and an Apollo client to be available.

To achieve the above, we update the src/index.js file that was created by create-react-app for us to set those things up:

Note that the above won’t really work properly yet as we don’t yet have a server yet, but it’s instructive to understand where we are heading.

Test the App component

We can the App component in exactly the same way that we tested our container components earlier. We can write stories that both mock out the GraphQL server responses and the router state.

For instance, if we wanted to write a story for the homepage, it would look like:

This test is very similar to the test we built for the InboxScreen but lets us render the complete app with a fixed set of data and test that it looks OK. We’ve managed to get to the point of rendering the complete app, with sensible data, without ever touching the server. Hopefully if we’ve designed our schema well, we’ll be able to slot the real server in and things will just work!

Next Steps

We’ve now built our frontend application all the way out to the level of a complete routed application that passes data all the way down the stack without even touching or configuring a server. This has worked great for ensuring that static data renders the correct way and that routes work correctly.

However, the time has come to build a GraphQL server. In the final post of this series, we’ll use a simple server generating tool called create-graphql-server to generate this server, and start using real data with our application.

GraphQL & React tutorial (part 6/6)
Learn to write a simple GraphQL server for Taskbox

Did this article help you?

Get free UI development guides and tutorials like this emailed to you.

4,476 developers and counting

We’re hiring!

Join the team behind Storybook and Chromatic. Build tools that are used in production by 100s of thousands of developers. Remote-first.

View jobs

Popular posts

What we learned from getting acquired by Meteor

Part I — Valuations and the acquisition mechanics.
loading
Zoltan Olah
Product
PricingAboutJobsTerms of ServicePrivacyStatusSecurity • SOC 2Contact Sales
Chromatic
© Chroma Software Inc. Made by the maintainers of Storybook.