Learning LogoLearning Logo
Learning LogoLearning Logo
Copy article linkShare Article on TwitterShare article on LinkedInShare article on FacebookShare article on Pinterest

React Hooks in Apollo Client for GraphQL Queries and Mutations

David Mráz
David Mráz@davidm_ai
Author's Linkedin accountAuthor's Instagram accountAuthor's Twitter accountAuthor's Twitter account
Development

Introduction

The hype following the last React conference has diminished. The proposal for React Hooks was introduced as part of the React alpha release. Since React v16.8, React Hooks have been in all official release as well. How does this improvement affect how we build our apps with GraphQL? We usually take our time before introducing new tech features in our projects at Atheros. This will allow us to not jump on the false hypes. React Hooks are now tested and production ready, so we took a shot on implementing it in our projects as well. React Hooks is a new way how to reduce the need for React component classes and their life-cycle methods. They also solve other problems related to using HOC (Higher order component) or render props pattern. There are a lot of resources on React Hooks and I will not go deep into them from React standpoint in this article. You can checkout the following talk from the recent conference

or the official documentation

The official React library comes with its own default Hooks such as useState, useContext, useEffect and others. But, The React library does not, however, contain Hooks for executing GraphQL queries and mutations in the Apollo client. Let's take a look at these now. First, let's summarize how we currently fetch data with Apollo and React.

Apollo client API

With the Apollo client and React, you can query your GraphQL server in various ways. We currently have three major ways to query our GraphQL server.

  • HOC pattern
  • Render props pattern
  • React Hooks

We will show how these approaches work using the simple component for displaying a list of emails. The GraphQL query looks like this:

query Subscriptions {
subscriptions {
id
email
source
}
}

It will be useful to check out the repository with examples. You can clone the repository with...

git clone git@github.com:atherosai/next-react-graphql-apollo-hooks.git

and then, to preserve package-lock.json dependencies, install with...

npm ci

You can run dev server as follows...

npm run dev

HOC (Higher-Order-Component) pattern

As far as I know, this is the oldest execution method for queries and mutations with Apollo. It uses the well-known React HOC pattern. This pattern is implemented in React Apollo using the HOC component created with the graphql function. We can use this function to define further HOCs for a different GraphQL query or mutation. With this approach, we can write our simple component as follows:

import React from 'react';
import { graphql } from '@apollo/react-hoc';
import get from 'lodash.get';
import SUBSCRIPTIONS_QUERY from './Subscriptions.graphql';
import s from './SubscriptionTable.scss';
const withSubscriptionQuery = graphql(SUBSCRIPTIONS_QUERY);
const SubscriptionsTable = ({ data }) => {
return (
<div className={s.SubscriptionTable}>
<div className={s.SubscriptionTable__Header}>Email</div>
{get(data, 'subscriptions', []).map(subscription => (
<div key={get(subscription, 'id')} className={s.SubscriptionTable__Row}>
{get(subscription, 'email')}
</div>
))}
</div>
);
};
export default withSubscriptionQuery(SubscriptionsTable);

The disadvantage can be that if we have many mutations or queries, it can become impractical to maintain so many HOCs. In some cases, you even need to keep the HOCs in order if you use, for example, the with Apollo component as well. In these cases, to clean up the code we can use compose utility from the React Apollo package, or just use the recompose library.

Render props pattern

This pattern was official one for quite some time in Apollo community. There is no need to wrap the components with HOCs. The HOCs created with graphql() had been replaced by the Query and Mutation components. The rewrite for our simple component above is easy.

import React from 'react';
import { Query } from '@apollo/react-components';
import get from 'lodash.get';
import SUBSCRIPTIONS_QUERY from './Subscriptions.graphql';
import s from './SubscriptionTable.scss';
const SubscriptionsTable = () => {
return (
<Query query={SUBSCRIPTIONS_QUERY}>
{({ loading, error, data }) => {
if (loading) return "Loading...";
if (error) return \`Error! ${error.message}\`;
return (
<div className={s.SubscriptionTable}>
<div className={s.SubscriptionTable__Header}>Email</div>
{get(data, 'subscriptions', []).map(subscription => (
<div key={get(subscription, 'id')} className={s.SubscriptionTable__Row}>
{get(subscription, 'email')}
</div>
))}
</div>)
}}
</Query>
);
};
export default SubscriptionsTable;

You are still able to use either HOCs or render props in Apollo client, but both ways are now obsolete and replaced with official React Hooks. You can also check out this article on pros and cons of higher-order components, render props, and React Hooks.

Using React Hooks with GraphQL in the Apollo client

This section was significantly updated to be compliant with official Apollo client API

React Hooks now have official support in the React Apollo. The support of React Hooks is coming with some great improvements. First of all there is significant reduction in bundle size as you can use only @apollo/react-hooks package. Even though you can reinvent the wheel and try to prepare the Hooks by yourself, I would suggest to use already-prepared Hooks. With the new introduction of React Hooks in Apollo official release you would need to install dedicated packages for SSR. The first thing that you need to do is to wrap your top-level component with the Apollo provider. We use Next.js in our example project, so a good place to do it can be in __app.js file as follows:

import React from 'react';
import get from 'lodash.get';
import App, { Container } from 'next/app';
import { ApolloProvider } from '@apollo/react-hooks';
import Router from 'next/router';
import { pageview } from '../lib/gtag';
import { NODE_ENV, CUSTOM_ENV } from '../config/config';
import withApolloClient from '../lib/with-apollo-client';
import globalStyle from '../theme/global.scss';
if (CUSTOM_ENV === 'production') {
Router.onRouteChangeComplete = url => {
pageview(url);
};
}
class MyApp extends App {
componentDidMount() {
import('webfontloader').then(WebFont =>
WebFont.load({
google: {
families: ['Montserrat']
}
})
);
if ('serviceWorker' in navigator && NODE_ENV === 'production') {
get(navigator, 'serviceWorker').register('/service-worker.js');
}
}
render() {
const { Component, pageProps, apolloClient } = this.props;
return (
<Container>
<ApolloProvider client={apolloClient}>
<div className={globalStyle.Global}>
<Component {...pageProps} />
</div>
</ApolloProvider>
</Container>
);
}
}
export default withApolloClient(MyApp);

However, this is only good approach if you use Next.js with server side rendering (SSR) and fetch data from GraphQL on majority of your pages. For pages that do not need to be server side rendered you can use HOC to inject Apollo Provider on per page bases as follows:

import React from 'react';
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient from 'apollo-client';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { IS_PROD } from '../config/config';
import initApollo from './init-apollo';
interface AppPropsI {
apolloClient: ApolloClient<NormalizedCacheObject>;
}
interface ApolloPropsI {
apolloState: NormalizedCacheObject;
}
// should be used for pages, which should not be server side renderer
const withApolloClientStatic: Function = (App: React.FunctionComponent<AppPropsI>) => {
const Apollo: React.FunctionComponent<ApolloPropsI> = ({ apolloState }: ApolloPropsI) => {
const apolloClient = initApollo(apolloState);
return (
<ApolloProvider client={apolloClient}>
<App apolloClient={apolloClient} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (!IS_PROD) {
const displayName = Apollo.displayName || Apollo.name || 'Component';
if (displayName === 'App') {
// eslint-disable-next-line no-console
console.warn('This withApollo HOC only works with PageComponents.');
}
Apollo.displayName = `withApollo(${displayName})`;
}
return Apollo;
};
export default withApolloClientStatic;

The Apollo Provider enables us to use React Hooks for executing queries and mutations in our application. The following Hooks are available in the official Apollo release: useQuery, useLazyQuery, useMutation, useSubscription and useApolloClient.

GraphQL queries with React Apollo Hooks

Let's take a look at the component for fetching emails that we wrote with the HOC and render props pattern. We will import the useQuery Hook from the officialReact Apollo Hooks library. Now let's define our first Hook for a GraphQL query. Hooks need to be defined in the body of functional React components. The new implementation with React Hooks is as follows:

import React from 'react';
import get from 'lodash.get';
import uuid from 'uuid/v1';
import { useQuery } from '@apollo/react-hooks';
import SUBSCRIPTIONS_QUERY from './SUBSCRIPTIONS.graphql';
import { SubscriptionsQuery, SubscriptionsQueryVariables } from '../../../__generated__/typescript-operations';
import s from './SubscriptionsTable.scss';
const SubscriptionsTable: React.FunctionComponent = () => {
const { data, loading, error } = useQuery<SubscriptionsQuery,
SubscriptionsQueryVariables>(SUBSCRIPTIONS_QUERY);
if (loading) return <>Loading...</>;
if (error) return <>{`Error! ${error.message}`}</>;
return (
<div className={s.SubscriptionTable}>
<table>
<thead>
<tr>
<th>Email</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{data && data.subscriptions && data.subscriptions.map((subscription) => (
<tr key={get(subscription, 'id', uuid())}>
<td>
{get(subscription, 'email')}
</td>
<td>
{get(subscription, 'source')}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default SubscriptionsTable;

We can see that the API is simple to use, and also the useQuery Hook returns the same variables as usual. You might also notice that we have pre-generated our TypeScript types. For more information you can follow our previous article.

Now let's take a look at how we can define variables and manually update the cache.

Writing our GraphQL mutations with React Hooks

We can define the useMutation in a similar way. As we know, the main difference between a query and a mutation lies in their different execution. Queries are executed in parallel, but mutations are executed serially. Let's take a look at how to execute the subscribeEmail mutation

mutation Subscribe($input: SubscribeInput!) {
subscribe(input: $input) {
id
email
source
}
}

using the useMutation Hook.

/* eslint-disable jsx-a11y/label-has-for */
import React from 'react';
import { useMutation } from '@apollo/react-hooks';
import {
Formik, ErrorMessage, Form, Field,
} from 'formik';
import * as Yup from 'yup';
// eslint-disable-next-line import/no-extraneous-dependencies
import { FetchResult } from 'apollo-link';
import get from 'lodash.get';
import s from './Subscription.scss';
import SUSCRIBE_MUTATION from './SUBSCRIBE.graphql';
import SUBSCRIPTIONS_QUERY from '../SubscriptionsTable/SUBSCRIPTIONS.graphql';
import { SubscribeMutation, SubscribeMutationVariables, Query } from '../../../__generated__/typescript-operations';
interface InitialValuesI {
email: string;
}
interface HandleSubscribeI {
values: InitialValuesI;
subscribeMutation: Function;
resetForm: Function;
}
const handleSubsribe: Function = async ({
values,
subscribeMutation,
resetForm,
}: HandleSubscribeI) => {
const subscribeResult: Promise<SubscribeMutation> = await subscribeMutation({
variables: {
input: {
source: 'HOME_PAGE',
...values,
},
},
});
if (get(subscribeResult, 'data.subscribe')) {
resetForm();
}
return subscribeResult;
};
const Subscription: React.FunctionComponent = () => {
const [subscribeMutation] = useMutation<SubscribeMutation, SubscribeMutationVariables>(
SUSCRIBE_MUTATION,
{
update: (cache, { data }: FetchResult): void => {
const dataResult = cache.readQuery<Query>({ query: SUBSCRIPTIONS_QUERY });
cache.writeQuery({
query: SUBSCRIPTIONS_QUERY,
data: {
subscriptions: dataResult
&& dataResult.subscriptions
&& dataResult.subscriptions.concat([data && data.subscribe]),
},
});
},
},
);
const initialValues: InitialValuesI = {
email: '',
};
return (
<div className={s.Subscription}>
<div className={s.Subscription__SubscriptionWrapper}>
<div>
<h2>
Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing
industries for previewing layouts and visual mockups.
</h2>
<Formik
initialValues={initialValues}
onSubmit={async (values, { resetForm }): Promise<SubscribeMutation> => handleSubsribe({
values,
subscribeMutation,
resetForm,
})}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required('Before submitting you need to provide your email'),
})}
>
<Form>
<div className={s.Subscription__Row}>
<label htmlFor="email">Email</label>
<Field
id="email"
className={s.Carousel__EmailInput}
name="email"
placeholder="your@email.com"
type="email"
/>
<button type="submit" className={s.Subscription__SubscribeButton}>
Subscribe
</button>
</div>
<div className={s.Subscription__FieldErrorRow}>
<ErrorMessage
name="email"
component="div"
className={s.Subscription__FieldError}
/>
</div>
</Form>
</Formik>
</div>
</div>
</div>
);
};
export default Subscription;

We have written our component with the excellent Formik and Yup validation library. We can see that the Hooks definition is done without variables. In the React Apollo library, we can either define variables in the body of a functional component or pass them dynamically once the Hook is executed.

Conclusion

I hope that you like this short article on using React Hooks with GraphQL. We really enjoy using Hooks with unofficial community library and now with official Apollo support it gets even better. To ease your Hooks set-up with Apollo, you can use our example repository to speed up the process.

Did you like this post? You can clone the repository with the examples and project set-up here. Feel free to send any questions about the topic to david@atheros.ai.

Atheros Learning Logo

Atheros Newsletter

Subscribe to our newsletter, and don't miss a beat!

message

* Signing up for Atheros newsletter indicates you agree with

Terms and Conditions and Privacy Policy including our Cookie Policy.

Ready to take next step?

Unlock your potential and master the art of development and design by joining our Classes today.

Don't miss out on the opportunity to enhance your skills and create a bright future in the digital world like thousands of others.