Pup

v2.x

GraphQLPerforming Queries & Mutations

With our queries and mutations defined in our schema, now we can put them to use on the client.

In Pup, we've utilized the swydo:graphql package to allow us to create .gql files containing our queries and mutations on the client. This is helpful because queries and mutations can be anywhere from a couple to several lines and keeping them separate from components keeps things tidy. Of note, all GraphQL related client code is located in the /ui directory:

  • /ui/fragments reusable "chunks" of fields that are used in multiple queries.
  • /ui/mutations mutation definitions for the client.
  • /ui/queries query definitions for the client.

Imports of these files change based on their contents

Each .gql file can have one or more fragments, queries, or mutations. For files with a single definition, a JavaScript default import must be used (no curly braces like import queryName from '../../queries/Type.gql';), while files with multiple definitions must use named imports (with curly braces like import { queryOne, queryTwo } from '../../queries/Type.gql';).

To put queries and mutations to use in our component code, we recommend using the graphql() component enhancer (a.k.a. as a data container). This is in contrast to the <Query /> and <Mutation /> components that are included in the react-apollo package where the graphql() enhancer is imported from.

The reason we chose the enhancer over the components in Pup is purely aesthetic, not technical. We found that the component approach worked just fine, but muddied up components and made it difficult to see the boundary between loading data and presenting markup. With enhancers, all of our data loading code is kept at the bottom of our file near our export and our presentation code stays totes cozy in the render() method.

On the right, we can see two examples: loading a single query on a component and loading a query and a mutation together. These examples are interchangeable. The single query could also be a single mutation and the query and mutation example could be two mutations.

Multiple graphql() enhancers require the compose() method

Notice that in our second example where we combine a query and a mutation together, we've introduced the compose() method to combine our two graphql() calls together.

Notice that for queries, the response is loaded via the data.<fieldBeingQueried> prop and the mutation is loaded via a prop matching the name property on the mutation's options object (here, removeBlogPost).

Example Query File
query blogPosts {
  blogPosts {
    _id
    title
    createdAt
  }
}
Example Mutation File
mutation removeBlogPost($_id: String!) {
  removeBlogPost(_id: $_id) {
    _id
  }
}
Using a Single Query
import React from 'react';
import { Link } from 'react-router-dom';
import { graphql } from 'react-apollo';
import PropTypes from 'prop-types';
import blogPostsQuery from '../../queries/BlogPosts.gql';
import { monthDayYear } from '../../../modules/dates';

const BlogPosts = ({ data }) => (
  <React.Fragment>
    <h1>Blog Posts</h1>
    {data && data.blogPosts && data.blogPosts.map(({ _id, title, createdAt }) => (
      <div key={_id}>
        <p>Posted on {monthDayYear(createdAt)}</p>
        <h2>{title}</h2>
        <Link to={`/posts/${_id}`}>View Post</Link>
      </div>
    ))}
  </React.Fragment>
);

BlogPosts.propTypes = {
  data: PropTypes.object.isRequired,
};

export default graphql(blogPostsQuery)(BlogPosts);
Using a Query and Mutation
import React from 'react';
import { Link } from 'react-router-dom';
import { compose, graphql } from 'react-apollo';
import PropTypes from 'prop-types';
import blogPostsQuery from '../../queries/BlogPosts.gql';
import removeBlogPostMutation from '../../mutations/BlogPosts.gql';
import { monthDayYear } from '../../../modules/dates';

const BlogPosts = ({ data, removeBlogPost }) => (
  <React.Fragment>
    <h1>Blog Posts</h1>
    {data && data.blogPosts && data.blogPosts.map(({ _id, title, createdAt }) => (
      <div key={_id}>
        <p>Posted on {monthDayYear(createdAt)}</p>
        <h2>{title}</h2>
        <Link to={`/posts/${_id}`}>View Post</Link>
        <Button bsStyle="danger" onClick={() => removeBlogPost({ variables: { _id } })}>Delete Post</Button>
      </div>
    ))}
  </React.Fragment>
);

BlogPosts.propTypes = {
  data: PropTypes.object.isRequired,
};

export default compose(
  graphql(blogPostsQuery),
  graphql(removeBlogPostMutation, {
    name: 'removeBlogPost',
  }),
)(BlogPosts);