Introduction to the Apollo GraphQL React Hooks Library

A quick start guide to migrate from react-apollo-hooks to the official React hooks library provided by Apollo

This post is an introduction to @apollo/react-hooks. It is not intended to be exhaustive. However, since the syntax is quite similar to react-apollo-hooks, it should be enough to get you started.

Getting Started

I have a react hooks app called DzHaven that I built entirely using React Hooks, no classes and no HOC. However, at the time @apollo/react-hooks was not available, so I used Daniel Trojanowski’s excellent react-apollo-hooks library for all my client-side code.

It works great, but one thing that always bothered me was the fact that the useQuery calls would run at declaration — instead of the exact moment I wanted. So as I go through my experience I’ll show you how to avoid unnecessary execution of queries, as well as some other useful tidbits.

If you are moving from react-apollo-hooks, the first thing you should know is that you do not need to do the migration in one shot.

react-apollo-hooks and @apollo/react-hooks can live together in the same client app. I did my migration running both providers and I did not see any issues. I’ve included my index.tsx file below with all of my imports, and as you can see, both providers are there and work without issue.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import configureStore from "./shared/store/configureStore";
import registerServiceWorker from "./registerServiceWorker";
import { Provider } from "react-redux";
import ErrorBoundary from "./shared/components/ErrorBoundary";
import Modal from "react-modal";
import ApolloClient, { InMemoryCache } from "apollo-boost";
import { ApolloProvider } from "@apollo/react-hooks";
import { ApolloProvider as ApolloHooksProvider } from "react-apollo-hooks";
import "bootstrap/dist/css/bootstrap.min.css";
import "./index.scss";
const apolloClient = new ApolloClient({
uri: process.env.REACT_APP_SERVER_URL,
credentials: "include",
cache: new InMemoryCache()
});
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<ApolloProvider client={apolloClient}>
<ApolloHooksProvider client={apolloClient}>
<ErrorBoundary>{[<App key="App" />]}</ErrorBoundary>
</ApolloHooksProvider>
</ApolloProvider>
</BrowserRouter>
</Provider>,
document.getElementById("root") as HTMLElement
);
Modal.setAppElement("#root");
registerServiceWorker();
view raw index.tsx hosted with ❤ by GitHub

The next thing you need to know is which packages are required for your particular setup. If you plan on supporting only hook functions and will not use the old class-style components and HOC, you can install just the @apollo/react-hooks package.

However, If you need to support all three, you’ll have to install the full react-apollo package. Or if you need either the HOC or older style React components you’ll need to install @apollo/react-hoc or @apollo/react-components respectively.

You should note that if you use only the @apollo/react-hooks as opposed to the full react-apollo package, your bundle size will drop to just 5.1kb from 10.6kb.

So then if you have the bare bones hooks setup, your npm install command would be like below. As shown in the previous code sample, apollo-boost provides the ApolloClient, the InMemoryCache, and some other items to help set up the React client and connect to your Apollo server. I would compare it roughly to using create-react-app in terms of it being a pre-packaged library providing most of what you need to set up, without extra effort.

npm install @apollo/react-hooks apollo-boost

Some Examples

Let’s start with queries. The Apollo library contains the same useQuery hook as the react-apollo-hooks library. They’re called identically so I won’t go over that here. However, there is a new hook called useLazyQuery, which allows delayed execution of your query. Let’s take a look at the code below.

const [
execGetThreadData,
{ called, data: getThreadData, error, loading, refetch }
] = useLazyQuery(GetThread);
useEffect(() => {
if (called && loading) {
log("thread is loading");
} else if (error) {
log(error);
} else if (!called && !getThreadData && id) {
execGetThreadData({
variables: { id, incViewCount: true }
});
} else if (called && getThreadData) {
if (thread == getThreadData.getThread) {
refetch({ id, incViewCount: true });
} else {
setThread(getThreadData.getThread);
}
}
}, [getThreadData, id, userProfile]);
view raw useLazyQuery.tsx hosted with ❤ by GitHub

Starting from the top we can see that the output of the query is a bit different. The function execGetThreadData is defined first and this is the method used to call your query when you desire to do so. The name given is of course up to you, but I like prefixing each query caller with “exec”.

After that, you can see multiple properties similar to what existed before, but with the addition of the called property. Basically this property prevents unwanted calls from being accidentally made.

Now if we look at useEffect, starting on line 6, we can see that it looks quite familiar to what we would have done before. However starting on line 11, we can see that we check the called property to make sure that a call on this query was not done yet and then make the call execGetThreadData. This is a minor difference but something to be aware of. Continuing on, I will say that most scenarios will not include the code starting on line 16. However, I wanted to show that refetching data is still possible using the same syntax when required.

For useMutation, the syntax is a bit different. As shown below:

const [execPostThread] = useMutation<any, any>(PostThreadMutation, {
refetchQueries: [
{
query: QueryMe
}
]
});
useEffect(() => {
if (askToPostThread && allowPostThread) {
setAskToPostThread(false);
execPostThread({
variables: {
threadId: localThread !== null ? localThread.id : "0",
userId: userProfile ? userProfile!.id : "0",
title,
body,
videoUrl,
bounty,
categoryId: selectedCategory!.id
}
})
.then(({ data }) => {
if (data.postThread && data.postThread.id > 0) {
const dataPostThread = data.postThread;
let message = "Thread posted successfully";
if (postBtnLabel === "Edit") {
message = "Thread edited successfully";
}
setValidationSuccessMsg(message);
setLocalThread(dataPostThread);
setTitle(dataPostThread.title);
setBody(dataPostThread.body);
setBounty(dataPostThread.bounty);
setPostBtnLabel("Edit");
if (window.location.href.includes("/postthread")) {
setThreadUrl("/thread/" + dataPostThread.id);
} else {
setThreadUrl("");
}
}
})
.catch(err => {
setSummaryValidationErrors([cleanGraphQlError(err.message)]);
});
}
}, [askToPostThread, allowPostThread]);
view raw useMutation.tsx hosted with ❤ by GitHub

Starting from the top again you can see a function property called execPostThread. Later on inside of useEffect I call this function to execute a mutation. On line 2 I wanted to show that refetchQueries is still supported with the same syntax as react-apollo-hooks. Starting on line 13, I am using the older promises style syntax because the call is from within a useEffect, which doesn’t allow awaiting. However if one is able to await the call then the syntax looks familiar again.

const { data, errors } = await execPostThread({
variables: {
threadId: localThread !== null ? localThread.id : "0",
userId: userProfile ? userProfile!.id : "0",
title,
body,
videoUrl,
bounty,
categoryId: selectedCategory!.id
}
});
if(errors && errors.length) {
// do something about error
} else {
// success
}
view raw useMutation2.tsx hosted with ❤ by GitHub

Obviously there’s much more than these features in the library but I hope this quick introduction will show you that getting started with @apollo/react-hooks should not be a massive rewrite of your code if you’ve been using hooks already. Having said that, testing has changed somewhat from the way I was doing it with react-apollo-hooks. Let me know if you would like to see a write up on that topic. Hope this helps.

If you’re a typical dev you probably write code for multiple platforms. If you want to use GraphQL on SwiftUI try this article.

As always if you like helping other devs try DzHaven.com

Published by David Choi

Developer Lead DzHaven.com. Where devs help devs and make money.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: