May 13th 2019 (9 days ago)

React hooks in Apollo client for GraphQL queries and mutations

Author profile picture
David Mráz (@david_mraz1)

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 allofficial 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 asuseState, 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/react-next-apollo-graphql.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 "react-apollo";
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 therecompose library.

Render props pattern

Currently, render props is the official way to handle GraphQL queries and mutations in Apollo. There is no need to wrap the components with HOCs. The HOCs created with graphql() have been replaced by the Query and Mutation components. The rewrite for our simple component above is easy.

import React from 'react';
import { Query } from "react-apollo";
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 both ways of executing queries in Apollo, but the render props pattern is the primary way in the official documentation. 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

React Hooks do not have official support in the React Apollo library (as of May 13, 2019), but you can watch opened issue on React Hooks in Apollo client. The community has already prepared unofficial implementation. For us it is completely sufficient, and we really love the simplicity of using it. The library is called React Apollo Hooks. Even though you can reinvent the wheel and try to prepare the Hooks by yourself, I would suggest to use already-prepared Hooks. The library also supports SSR and has experimental support for Suspense. The first thing that you need to do is to wrap your top-level component with the Hook 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 'react-apollo';
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-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}>
            <ApolloHooksProvider client={apolloClient}>
            <div className={globalStyle.Global}>
                <Component {...pageProps} />
            </div>
            </ApolloHooksProvider>
        </ApolloProvider>
        </Container>
    );
    }
}

export default withApolloClient(MyApp);
The Apollo Hook Provider enables us to use React Hooks for executing queries and mutations in our application. The following Hooks are available in the React Apollo Hooks package: useQuery, useMutation and useSubscription.

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 React 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 'react-apollo-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}>
      <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 Hook in a similar way. In a similar way we define useMutation Hook as well. 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.

import React from 'react';
import { useMutation } from 'react-apollo-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 Hooks library, we can either define variables in the body of a functional component or pass them dynamically once the Hook is executed. The API for updating the cache is also the same as in React Apollo.

Conclusion

I hope that you like this short article on using React Hooks with GraphQL. You should be aware that the final official implementation in React Apollo might be different from the community library. But I would give it a try if you are looking forward to using Hooks in your GraphQL projects. We really enjoy using it. 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. You can also check out our free upcoming GraphQL language course or subscribe for new articles on GraphQL Mastery.

Join thousands of others and receive our newsletter to get notified about new articles and get instant access to our upcoming free GraphQL Course

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