Skip to content

Latest commit

 

History

History
278 lines (229 loc) · 14.8 KB

mutations.md

File metadata and controls

278 lines (229 loc) · 14.8 KB
title description
Mutations
Learn how to update data with Mutation components

Now that we've learned how to fetch data with Apollo Client, what happens when we need to update that data? In this guide, you'll discover how to build Mutation components in order to send updates to your GraphQL server. You'll also learn how to update the Apollo cache after a mutation occurs and how to handle errors when things go wrong.

This page assumes some familiarity with building GraphQL mutations. If you'd like a refresher, we recommend reading this guide.

The following examples assume that you've already set up Apollo Client and have wrapped your React app in an ApolloProvider component. Read our getting started guide if you need help with either of those steps. Let's dive in!

If you'd like to follow along with the examples, open up our starter project on CodeSandbox and our sample GraphQL server on Launchpad. You can view the completed version of the app here.

The Mutation component

The Mutation component is what you'll use to trigger mutations from your UI. To create a Mutation component, just pass a GraphQL mutation string wrapped with the gql function to this.props.mutation and provide a function to this.props.children that tells React what to render. The Mutation component is an example of a React component that uses the render prop pattern. React will call the render prop function you provide with a mutate function and an object with your mutation result containing loading, error, called, and data properties. Let's look at an example:

import gql from "graphql-tag";
import { Mutation } from "react-apollo";

const ADD_TODO = gql`
  mutation addTodo($type: String!) {
    addTodo(type: $type) {
      id
      type
    }
  }
`;

const AddTodo = () => {
  let input;

  return (
    <Mutation mutation={ADD_TODO}>
      {(addTodo, { data }) => (
        <div>
          <form
            onSubmit={e => {
              e.preventDefault();
              addTodo({ variables: { type: input.value } });
              input.value = "";
            }}
          >
            <input
              ref={node => {
                input = node;
              }}
            />
            <button type="submit">Add Todo</button>
          </form>
        </div>
      )}
    </Mutation>
  );
};

First, create your GraphQL mutation, wrap it in gql, and pass it to the mutation prop on the Mutation component. The Mutation component also requires a function as a child (also called the render prop function). The first argument of the render prop function is the mutate function, which you call to tell Apollo Client that you'd like to trigger a mutation. The mutate function optionally takes variables, optimisticResponse, refetchQueries, and update; however, you can also pass in those values as props to the Mutation component. In the example, notice how we use the mutate function (called addTodo) to submit the form with our variables.

The second argument to the render prop function is an object with your mutation result on the data property, as well as booleans for loading and if the mutate function was called, in addition to error. If you'd like to ignore the result of the mutation, pass ignoreResults as a prop to the mutation component.

If you're following along with the example on CodeSandbox, you probably noticed that the UI reflecting the list of todos did not update with our newly created todo when you submitted the form. This is because the todos query in the Apollo cache does not know about our newly created todo. In the next section, we'll learn when and how to update the Apollo cache after a mutation.

Updating the cache

Sometimes when you perform a mutation, your GraphQL server and your Apollo cache become out of sync. This happens when the update you're performing depends on data that is already in the cache; for example, deleting and adding items to a list. We need a way to tell Apollo Client to update the query for the list of items. This is where the update function comes in! update functions aren't required to update the cache for all mutations, but our addTodo mutation is an example of where it comes in handy.

The update function is called with the Apollo cache as the first argument. The cache has several utility functions such as cache.readQuery and cache.writeQuery that allow you to read and write queries to the cache with GraphQL as if it were a server. There are other cache methods, such as cache.readFragment, cache.writeFragment, and cache.writeData, which you can learn about in our detailed caching guide if you're curious.

The second argument to the update function is an object with a data property containing your mutation result. If you specify an optimistic response, your update function will be called twice: once with your optimistic result, and another time with your actual result. You can use your mutation result to update the cache with cache.writeQuery.

Now that we've learned about the update function, let's implement one for the Mutation component we just built!

const AddTodo = () => {
  let input;

  return (
    <Mutation
      mutation={ADD_TODO}
      update={(cache, { data: { addTodo } }) => {
        const { todos } = cache.readQuery({ query: GET_TODOS });
        cache.writeQuery({
          query: GET_TODOS,
          data: { todos: todos.concat([addTodo]) }
        });
      }}
    >
      {addTodo => (
        <div>
          <form
            onSubmit={e => {
              e.preventDefault();
              addTodo({ variables: { type: input.value } });
              input.value = "";
            }}
          >
            <input
              ref={node => {
                input = node;
              }}
            />
            <button type="submit">Add Todo</button>
          </form>
        </div>
      )}
    </Mutation>
  );
};

You can pass the update function as a prop to Mutation or as an option to the mutate function (addTodo in this example). Since we need to update the query that displays a list of todos, first we read the data from the cache with cache.readQuery. Then, we concatenate our new todo from our mutation with the list of existing todos and write the query back to the cache with cache.writeQuery. Now that we've specified an update function, our UI will update reactively with the new todo once it comes back from the server.

Not every mutation requires an update function. If you're updating a single item, you usually don't need an update function as long as you return the item's id and the property you updated. While this may seem like magic, this is actually a benefit of Apollo's normalized cache, which splits out each object with an id into its own entity in the cache. Let's look at an example:

const UPDATE_TODO = gql`
  mutation updateTodo($id: String!, $type: String!) {
    updateTodo(id: $id, type: $type) {
      id
      type
    }
  }
`;

const Todos = () => (
  <Query query={GET_TODOS}>
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;

      return data.todos.map(({ id, type }) => {
        let input;

        return (
          <Mutation mutation={UPDATE_TODO} key={id}>
            {updateTodo => (
              <div>
                <p>{type}</p>
                <form
                  onSubmit={e => {
                    e.preventDefault();
                    updateTodo({ variables: { id, type: input.value } });

                    input.value = "";
                  }}
                >
                  <input
                    ref={node => {
                      input = node;
                    }}
                  />
                  <button type="submit">Update Todo</button>
                </form>
              </div>
            )}
          </Mutation>
        );
      });
    }}
  </Query>
);

If you try updating a todo, you'll notice that the UI updates immediately. Even though we don't plan on using the mutation return result in our UI, we still need to return the id and the property we updated in order for our UI to update reactively. Here, we don't need to specify an update function since the todos query will automatically reconstruct the query result with the updated todo's entry in the cache. If you'd like to dive deeper into the Apollo cache's normalization strategy, check out our advanced caching guide.

Loading and error state

How do we know that our mutation has completed? What happens when your mutation doesn't complete successfully? We need a way to track loading and error state. Luckily, the Mutation component allows you to do just that. Let's look at the Todos component from the previous example:

const Todos = () => (
  <Query query={GET_TODOS}>
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;

      return data.todos.map(({ id, type }) => {
        let input;

        return (
          <Mutation mutation={UPDATE_TODO} key={id}>
            {(updateTodo, { loading, error }) => (
              <div>
                <p>{type}</p>
                <form
                  onSubmit={e => {
                    e.preventDefault();
                    updateTodo({ variables: { id, type: input.value } });

                    input.value = "";
                  }}
                >
                  <input
                    ref={node => {
                      input = node;
                    }}
                  />
                  <button type="submit">Update Todo</button>
                </form>
                {loading && <p>Loading...</p>}
                {error && <p>Error :( Please try again</p>}
              </div>
            )}
          </Mutation>
        );
      });
    }}
  </Query>

In the render prop function, we can destructure loading and error properties off the mutation result in order to track the state of our mutation in our UI. The Mutation component also has onCompleted and onError props in case you would like to provide callbacks instead. Additionally, the mutation result object also has a called boolean that tracks whether or not the mutate function has been called.

Mutation API overview

If you're looking for an overview of all the props Mutation accepts and its render prop function, look no further! Most Mutation components will not need all of these configuration options, but it's useful to know that they exist. If you'd like to learn about the Mutation component API in more detail with usage examples, visit our reference guide.

Props

The Mutation component accepts the following props. Only mutation and children are required.

`mutation`: DocumentNode
A GraphQL mutation document parsed into an AST by `graphql-tag`. **Required**
`children`: (mutate: Function, result: MutationResult) => React.ReactNode
A function that allows you to trigger a mutation from your UI. **Required**
`variables`: { [key: string]: any }
An object containing all of the variables your mutation needs to execute
`update`: (cache: DataProxy, mutationResult: FetchResult)
A function used to update the cache after a mutation occurs
`ignoreResults`: boolean
If true, the `data` property on the render prop function will not update with the mutation result.
`optimisticResponse`: Object
Provide a [mutation response](../features/optimistic-ui.html) before the result comes back from the server
`refetchQueries`: (mutationResult: FetchResult) => Array<{ query: DocumentNode, variables?: TVariables}>
A function that allows you to specify which queries you want to refetch after a mutation has occurred
`onCompleted`: (data: TData) => void
A callback executed once your mutation successfully completes
`onError`: (error: ApolloError) => void
A callback executed in the event of an error
`context`: Record
Shared context between your Mutation component and your network interface (Apollo Link). Useful for setting headers from props or sending information to the `request` function of Apollo Boost.

Render prop function

The render prop function that you pass to the children prop of Mutation is called with the mutate function and an object with the mutation result. The mutate function is how you trigger the mutation from your UI. The object contains your mutation result, plus loading and error state.

Mutate function:

`mutate`: (options?: MutationOptions) => Promise
A function to trigger a mutation from your UI. You can optionally pass `variables`, `optimisticResponse`, `refetchQueries`, and `update` in as options, which will override any props passed to the `Mutation` component. The function returns a promise that fulfills with your mutation result.

Mutation result:

`data`: TData
The data returned from your mutation. It can be undefined if `ignoreResults` is true.
`loading`: boolean
A boolean indicating whether your mutation is in flight
`error`: ApolloError
Any errors returned from the mutation
`called`: boolean
A boolean indicating if the mutate function has been called

Next steps

Learning how to build Mutation components to update your data is an important part of developing applications with Apollo Client. Now that you're well-versed in updating data, why not try executing client-side mutations with apollo-link-state? Here are some resources we think will help you level up your skills:

  • Optimistic UI: Learn how to improve perceived performance by returning an optimistic response before your mutation result comes back from the server.
  • Local state: Manage your local state with Apollo by executing client-side mutations with apollo-link-state.
  • Caching in Apollo: Dive deep into the Apollo cache and how it's normalized in our advanced guide on caching. Understanding the cache is helpful when writing your mutation's update function!
  • Mutation component video by Sara Vieira: If you need a refresher or learn best by watching videos, check out this tutorial on Mutation components by Sara!