Skip to main content

Using React Hooks with GraphQL

4 min read

Older Article

This article was published 7 years ago. Some information may be outdated or no longer applicable.

Let’s take an existing React application that uses GraphQL and rip out the boilerplate by switching to React hooks.

I ran a few workshops for O’Reilly on React & GraphQL. When those workshops happened, React Hooks didn’t exist yet. The example here comes straight from that workshop material.

The base application

The codebase lives on GitHub.

The base application is simple: one page that lists cars. A standard React component pulls data from a RESTful API with a GraphQL layer sitting on top. I built the API and GraphQL service for demo purposes, so it’s intentionally bare-bones.

This article doesn’t cover GraphQL basics. If you want an introduction, read this article, or grab the Practical GraphQL: Become a GraphQL Ninja video course.

Here’s the React component listing all the cars:

import React, { Fragment } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import { Link } from '@reach/router';

export const GET_CARS = gql`
  {
    cars {
      id
      make
      model
    }
  }
`;

export default function Cars() {
  return (
    <Query query={GET_CARS}>
      {({ data, loading, error }) => {
        if (loading) return <p>Loading ...</p>;
        if (error) return <p>ERROR</p>;
        return (
          <Fragment>
            <h1>A list of cars 🚗</h1>
            {data.cars &&
              data.cars.map((car) => (
                <p key={car.id}>
                  {car.make} - {car.model} >>{' '}
                  <Link to={`/car/${car.id}`}>View more</Link>
                </p>
              ))}
          </Fragment>
        );
      }}
    </Query>
  );
}

If you’ve worked with React and GraphQL, you’ve seen this pattern. We store the query in GET_CARS, execute it through the <Query> component from react-apollo, and iterate over data.cars in the return statement.

The code works fine, but it’s already looking heavy for something this simple. Let’s trim it down.

React Hooks

Hooks landed in React 16.8. They gave functional components proper state management. Before hooks, you had to manage state through this.state in class components:

import React from 'react';
class MyClassComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

export default MyClassComponent;

With hooks, the same thing shrinks to this:

import React, { useState } from 'react';

function MyHookComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default MyHookComponent;

Same functionality, less code, easier to read. Notice we’ve also switched from a class component to a functional component.

Hooks solved the lifecycle methods and state management problem for functional components. Patterns like render-props and HOCs (Higher Order Components) aren’t necessary anymore.

Can you use Hooks inside class components? From the React Docs: “Hooks don’t work inside classes”.

Now that we’ve got hooks in our toolkit, let’s rewire the GraphQL implementation.

First, install the Apollo package: npm install @apollo/react-hooks. Then make two changes:

  • Update the <ApolloProvider> import
  • Rewrite the component’s data layer
// index.js
import { ApolloProvider } from '@apollo/react-hooks';
// ... code removed for brevity
ReactDOM.render(
  <ApolloProvider client={client}>
    <Pages />
  </ApolloProvider>,
  document.getElementById('root')
);

// cars.js
import React, { Fragment } from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
import { Link } from '@reach/router';

export const GET_CARS = gql`
  {
    cars {
      id
      make
      model
    }
  }
`;

export default function Cars() {
  const { data, loading, error } = useQuery(GET_CARS);
  if (loading) return <p>Loading ...</p>;
  if (error) return <p>ERROR</p>;
  return (
    <Fragment>
      <h1>A list of cars 🚗</h1>
      {data.cars &&
        data.cars.map((car) => (
          <p key={car.id}>
            {car.make} - {car.model} >>{' '}
            <Link to={`/car/${car.id}`}>View more</Link>
          </p>
        ))}
    </Fragment>
  );
}

Check out this blogpost to learn more about @apollo/react-hooks

In cars.js, we import useQuery from @apollo/react-hooks. The query itself stays the same. But the component is much cleaner. One line executes the query: const { data, loading, error } = useQuery(GET_CARS);. No more <Query> wrapper component. A big chunk of boilerplate, gone.

Conclusion

React hooks change how you build functional components and manage state. The Apollo GraphQL package takes full advantage, letting you strip away the ceremony around queries and data handling.