React Hooks for GraphQL Queries

Stefan Nagey
Capbase Engineering
7 min readJan 14, 2019

In this article, I’m going to show you how to create hooks to make working with graphQL a complete piece of cake!

Using React Hooks to tame GraphQL with AWS Amplify

If you’ve been following our journey with functional programming in Javascript with React, you’ll be all set, but if not, check out the beginning of our series for functional programming with react hooks.

The usage and concepts of GraphQL itself is beyond the scope of this article, and OPENGraphQL has a phenomenal set of resources in their publication that deal with GraphQL, Apollo, Prisma, Hasura, and AWS AppSync (the library that we’re using here at Capbase).

The combination of functional components, hooks, and GraphQL can make working with data from a GraphQL Backend source as simple as including information from a local file.

Why bother writing a hook?

If we write a hook to handle our queries, and manage their asynchronous nature, we can have our display code be much more straightforward, same as if we were getting our results passed in as props to the component.

Let’s take a look at how that’s going to look.

Using our Hook / A Sneak Peek at the Finished Product

When we’re done with our hook, it’ll look something like this when we want to go use it.

import GenericLoader from "componets/GenericLoader";
import { searchStarWarsFilms } from "graphql/queries";
import useGraphQuery from "useGraphQuery.js";

const MyDisplayComponent = () => {
const result = useGraphQuery(searchStarWarsFilms, {
filter: { episode: { eq: "IV" } },
});

const film = result && result.items && result.items[0];

return (film) ? (
<dl>
<dt>Episode</dt>
<dd>{film.episode}</dd>
<dt>Title</dt>
<dd>{film.title}</dd>
<dt>Characters</dt>
<dd>
<ul>
{film.characters.map( character => (
<li key={character.id}>
{character.name} portrayed by {character.actor}
</li>
))}
</ul>
</dd>
</dl>
) : <GenericLoader />;
};

While this example is pretty straightforward, let’s walk through it.

Using Amplify & AppSync to turn GraphQL schemae into AWS Resources

Let’s say that I’ve described an AppSync model along these lines (via AWS Amplify): ``` type StarWarsFilm @model @searchable { id: ID! Episode: String! Title: String! characters: [Character] @connection(name: “characters”) }

type FilmCharacter @model @searchable { id: ID! AppearsIn: StarWarsFilm! @connection(name: “characters”) Name: String Actor: String SalaryForFilm: Number } ```

This will auto-generate for me, the following resources: * Two DynamoDB tables, one named StarWarsFilms and another named FilmCharacters * An ElasticSearch instance * A lambda to stream data from the DynamoDB table into the ElasticSearch instance

Amplify will also create for me the following useful Javascript variables (as strings that describe GraphQL operations): * Queries to list all of each model * Queries to get an individual model * Queries to search by an attribute of a model * Mutations for CRUD

As always, you can define your own query and mutation strings for specific attributes that you want to get, but you get a whole bunch nicely auto-generated for you.

Walking through the example usage of the finished hook

OK, going back to the example, my first three lines are importing first a generic loader, then the auto-generated query string to search for a Star Wars Movie, and finally the hook that we’re going to create in the next section.

Then we define our functional component, and then I assign a value to the result of our hook.

The arguments for the hook are first the query that you want to use, and second, any parameters that you want to pass into the query. If we are searching or getting a model, then we need to pass in either a filter for the search, or the ID that we want to get.

Given the functional nature of what we do, when the value of the hook changes (that is, when we get our reply from the server) our component will re-execute and then that value will be available.

This can be seen from our return. Before we get our return from the GraphQL server, the value will be undefined. After we get it, this value with be a Javascript object, with a property named items which will contain a list of all the items which match our search. Since I know that in our dataset, searching for episode "IV" will give us a single response, I'm gonna be a cowboy and just display the first result of the filter.

This first item in the list will be an object that describes our movie, and which contains a list of the characters in that movie.

From there, we’re just displaying these data in a dictionary list, but you can choose to use them anyway you like.

Writing the hook useGraphQuery hook

Our hook is going to do three things. 1. Use underlying React hooks to manage state and effects 2. Interact with the underlying GraphQL API, we’re using the AppSync api, you can also use Apollo, or the library of your choice 3. Handle our inputs and outputs in a developer-friendly way that is useful to our downstream devs

The code for the useGraphQuery hook

OK, let’s have a look at the raw code, then we’ll go through it line-by-line

import { useEffect, useState } from "react";
import { API, graphqlOperation } from "aws-amplify";

export default (query, queryParams) => {
const [queryResult, updateQueryResult] = useState(undefined);

const runQuery = async () => {
try {
const queryData = await API.graphql(
graphqlOperation(query, queryParams)
);
let queryName = query.match(/^query (.+)\((.*)/)[1];
queryName = `${queryName[0].toLocaleLowerCase()}${queryName.substr(1)}`;
updateQueryResult(queryData.data[queryName]);
} catch (err) {
// We wouldn't actually handle errors like this
console.log("error: ", err);
}
};

useEffect(() => {
runQuery();
}, []);

return queryResult;
};

Alright, here’s what’s happening here.

Imports: the first two lines import the hooks that we’re going to use from React, in this case, useState and useEffect, and then the pieces of the Amplify library that we're going to use to interact with AppSync.

Our parameters into this function are, as mentioned earlier, are our query string, and then the parameters into that query string.

Now, what you’ll notice we do next is kind of interesting. We’re defining a piece of state to hold the result of our query. This means that we can later assign the result here and then the consumers of this hook will get re-called when this value updates. This piece of state is actually going to be the return value from our hook.

Next, we define the function that is going to handle the API interaction as an asynchronous function. If you look further down, you’ll see that that’s because we’re going to call this from the useEffect call.

We can’t have the useEffect callback itself be asynchronous, so we have to define it separately and then call it in there. You can check out my article on async functional programming with javascript for more on how this is working.

If we look at how our runQuery function works, we see that we're first preparing the query with the inner call to graphqlOperation which takes our query string, and the parameters thereto. That is then passed into the API call, which is asynchronous, and which we are await-ing here.

Next is just some syntactic sugar that we’re giving to our consumers. Since GraphQL is going to wrap the result of the query in an object whose member matches the name of the query, we’re just unwrapping that, and proving the result bare.

Then we’re going to update the result. This makes sure that the components consuming this hook are called again when this value changes. This is actually the magic that lets our downstream consumers use this so beautifully.

Lastly, we’re going to handle our errors, which we’re just logging to console here, but that hopefully you’ve got a better way to deal with.

Now we just call our function using useEffectnote if we wanted to make sure that the query were re-run when the parameters changed, then, instead of passing [] into useEffect, we would instead pass [queryParams].

Then we return the stateful value to our consumer, so that they can be updated when we get our result.

Why bother with this?

Since so much of what we do with async programming is boilerplate, it makes sense to extract as much of this boilerplate as we can, and also make it as easy to consumer these resources as possible in the future.

So here, we’ve reduced the complexity of interacting with a remote api, error handling, and async operations into a single call that lets our consumers treat our remote database just like a local synchronous function.

Pretty neat, eh?

What’s next?

Next time, we’ll look at how to do this same thing, but with mutations. In the future, I’ll show you how to create a hook for subscription as well.

I hope that this article has been useful to you. For more on how to work with react hooks and functional programming in javascript, check out some of the articles in the series.

Thanks, and please let me know any questions, comments, or complaints in the section below!

About the Author

Stefan Nagey is Co-Founder of Capbase and Dharma. In a previous life he has variously promoted motion pictures, been a diplomat and registered foreign agent, and run payment processing in the online gambling space. He variously writes about engineering and the headaches of being a startup founder in the 21st century.

--

--

Stefan Nagey
Capbase Engineering

Data geek. Capbase.com and Dharma.ai Co-Founder. Passionate about scalability, governance, cycling, and poker.