May 13th 2019

React hooks in Apollo client for GraphQL queries and mutations

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 lifecycle 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
  }
}

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 withApollo 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 this is in __app.js file.

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);
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 { useQuery } from '@apollo/react-hooks';
import get from 'lodash.get';
import SUBSCRIPTIONS_QUERY from './Subscriptions.graphql';
import s from './SubscriptionTable.scss';

const SubscriptionsTable = () => {
  const { data, loading, error } = useQuery(SUBSCRIPTIONS_QUERY);

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <div className={s.SubscriptionTable}>
      <h2>React Hooks</h2>

      <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 SubscriptionsTable;

We can see that the API is simple to use, and also the useQuery Hook returns the same variables as usual. 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 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';
import { get } from 'lodash';
import s from './Subscription.scss';
import SUSCRIBE_MUTATION from './Subscribe.graphql';
import SUBSCRIPTIONS_QUERY from '../SubscriptionsTable/Subscriptions.graphql';

const handleSubsribe = async ({ values, subscribeMutation, resetForm }) => {
  const subscribeResult = await subscribeMutation({
    variables: { input: values }
  });
  if (get(subscribeResult, 'data.subscribe')) {
    resetForm();
  }
};

const Subscription = () => {
  const [subscribeMutation] = useMutation(SUSCRIBE_MUTATION, {
    update: (cache, { data: { subscribe } }) => {
      const { subscriptions } = cache.readQuery({ query: SUBSCRIPTIONS_QUERY });
      cache.writeQuery({
        query: SUBSCRIPTIONS_QUERY,
        data: {
          subscriptions: subscriptions.concat([subscribe])
        }
      });
    }
  });

  return (
    <div className={s.Subscription} name="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={{
              email: ''
            }}
            onSubmit={async (values, { resetForm }) =>
              handleSubsribe({
                values,
                subscribeMutation,
                resetForm
              })
            }
            validationSchema={Yup.object().shape({
              email: Yup.string()
                .email()
                .required('Before submitting you need to provide your email')
            })}
            render={() => (
              <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>
            )}
          />
        </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 unoffical community library and now with official Apollo support it gets even better. To ease your Hooks setup with Apollo, you can use our example repository to speed up the process.

Feel free to send any questions about the topic to david@atheros.ai.

Join thousands of others and be occasionally notified about new articles & news from engineering world.

* Signing up for GraphQL Mastery newsletter indicates you agree with Terms and Conditions and Privacy Policy including our Cookie Policy.