/
Launch Apollo Studio

Update data with mutations

Learn how to update data with the useMutation hook


Time to accomplish: 10 Minutes

Now that we've added multiple queries to our React app, let's add a mutation. The process is similar, with a few important differences.

As a reminder, mutations are GraphQL operations that can modify back-end data (unlike queries). Our server's schema supports the following mutations:

# YOU DON'T NEED TO COPY THIS.
type Mutation {
  bookTrips(launchIds: [ID]!): TripUpdateResponse!
  cancelTrip(launchId: ID!): TripUpdateResponse!
  login(email: String): String # login token
}

We'll start by implementing the ability to log in. Notice that this mutation accepts a single variable, email.

The code blocks below use TypeScript by default. You can use the dropdown menu above each code block to switch to JavaScript.

If you're using JavaScript, use .js and .jsx file extensions wherever .ts and .tsx appear.

Support user login

Note: For simplicity, our example application doesn't implement actual user accounts with password-based authentication. Instead, a user unsecurely "logs in" by submitting their email address and receiving a corresponding session token from the server.

Define the mutation

To start, navigate to src/pages/login.tsx and replace its contents with the following:

src/pages/login.tsx
import React from 'react';
import { gql, useMutation } from '@apollo/client';

import { LoginForm, Loading } from '../components';
import * as LoginTypes from './__generated__/login';

export const LOGIN_USER = gql`
  mutation Login($email: String!) {
    login(email: $email) {
      id
      token
    }
  }
`;

Our LOGIN_USER definition looks just like our queries from the previous section, except it replaces the word query with mutation. We receive a User object in the response from login, which includes two fields that we'll use:

  • The user's id, which we'll use to fetch user-specific data in future queries
  • A session token, which we'll use to "authenticate" future GraphQL operations

Apply the useMutation hook

We'll use Apollo Client's useMutation React Hook to execute our LOGIN_USER mutation. As with useQuery, the hook's result provides properties that help us populate and render our component throughout the mutation's execution.

Unlike useQuery, useMutation doesn't execute its operation as soon as its component renders. Instead, the hook returns a mutate function that we call to execute the mutation whenever we want (such as when the user submits a form).

Add the following to the bottom of login.tsx:

src/pages/login.tsx
export default function Login() {
const [login, { loading, error }] = useMutation<
  LoginTypes.login,
  LoginTypes.loginVariables
>(LOGIN_USER);

if (loading) return <Loading />;
if (error) return <p>An error occurred</p>;

return <LoginForm login={login} />;
}
  • The first object in useMutation's result tuple (login) is the mutate function we call to execute the mutation. We pass this function to our LoginForm component.
  • The second object in the tuple is similar to the result object returned by useQuery, including fields for the operation's loading and error states and the operation's result data.

Now whenever a user submits the login form, our login mutation is called. The user's token is stored in the in-memory cache, however we want that token to be available across multiple visits in the same browser. Let's tackle that next.

Persist the user's token and ID

In our call to useMutation, we can include an onCompleted callback. This enables us to interact with the mutation's result data as soon as it's available. We'll use this callback to persist the user's token and id.

Modify the useMutation call in login.tsx to match the following:

src/pages/login.tsx
const [login, { loading, error }] = useMutation<
  LoginTypes.Login,
  LoginTypes.LoginVariables
>(
  LOGIN_USER,
  {
    onCompleted({ login }) {
      localStorage.setItem('token', login.token as string);
      localStorage.setItem('userId', login.id as string);
    }
  }
);

Our onCompleted callback stores the user's unique ID and session token in localStorage, so we can load these values into the in-memory cache the next time the user visits our application. We'll add that functionality in the next chapter.

Add Authorization headers to all requests

Our client should provide the user's token with each GraphQL operation it sends to our server. This enables the server to verify that the user has permission to do what they're trying to do.

In index.tsx, let's modify the constructor of ApolloClient to define a default set of headers that are applied to every GraphQL request:

src/index.tsx
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
  cache,
  uri: 'http://localhost:4000/graphql',
  headers: {    authorization: localStorage.getItem('token') || '',  }});

Our server can ignore the token when resolving operations that don't require it (such as fetching the list of launches), so it's fine for our client to include the token in every request.

Enable the login form

We're finished defining our login mutation, but we don't yet display the form that enables a user to execute it. Because we're storing the user token locally, we'll use Apollo Client's local state APIs to power some of the form's logic in the next section.

Edit on GitHub