Now that we've learned how to fetch data, the next step is to learn how to update that data with mutations.
Let's define our GraphQL Mutation document.
import { gql } from 'glimmer-apollo';
export const CREATE_NOTE = gql`
mutation CreateNote($input: NoteInput!) {
createNote(input: $input) {
id
title
description
}
}
`;
export type CreateNoteMutation = {
__typename?: 'Mutation';
createNote?: {
__typename?: 'Note';
id: string;
title: string;
description: string;
} | null;
};
export type CreateNoteMutationVariables = {
input: {
title: string;
description: string;
isArchived?: boolean | null;
};
};
Similar to useQuery, useMutation is a utility function to create a Mutation Resource.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [
CREATE_NOTE,
{
/* options */
}
]
);
}
this is to keep track of destruction. When the context object (this) is destroyed, all the mutations resources attached to it can be destroyed.mutate function is called.useMutation should always be a function that returns an array.import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
CREATE_NOTE,
{
/* options */
}
]);
submit = async (): Promise<void> => {
await this.createNote.mutate(
input: {
title: 'Title',
description: 'Description',
isArchived: false
}
});
};
static template = hbs`
<button {{on "click" this.submit}}>
Create Note
</button>
{{#if this.createNote.loading}}
Creating...
{{else if this.createNote.error}}
Error!: {{this.createNote.error.message}}
{{else if this.createNote.called}}
<div>
id: {{this.createNote.data.createNote.id}}
Title: {{this.createNote.data.createNote.title}}
Description: {{this.createNote.data.createNote.description}}
</div>
{{/if}}
`;
}
In the example above, we call the mutate function to execute the GraphQL mutation in the backend API.
The mutate function returns a Promise that resolves with the received data. It's important to note that it will not throw if an error occurs, making the use of try catch not possible with the mutate function.
There are two ways you can pass variables to mutations.
useMutation.mutate.Which one should you use? It depends on how you are getting the data for your mutation.
It probably makes sense to pass in the mutate call if it comes from a form.
However, some data might be present early on, so you might also want to pass these variables in the useMutation. Glimmer Apollo does a shallow merge on variables provided earlier, with these provided at mutate time.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [
CREATE_NOTE,
{
variables: {
/* default variables here */
}
}
]
);
submit = async (): Promise<void> => {
await this.createNote.mutate({
/* overwrite default variables here */
});
};
}
Similar to variables, you can pass options to mutations on useMutation and mutate function call.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [
CREATE_NOTE,
{
errorPolicy: 'all'
}
]
);
submit = async (): Promise<void> => {
await this.createNote.mutate(
{
/* variables */
},
/* additional options */
{ refetchQueries: ['GetNotes'] }
);
};
}
clientIdThis option specifies which Apollo Client should be used for the given mutation. Glimmer Apollo supports defining multiple Apollo Clients that are distinguished by a custom identifier while setting the client to Glimmer Apollo.
// ....
setClient(
this,
new ApolloClient({
/* ... */
}),
'my-custom-client'
);
// ....
notes = useMutation(this, () => [
CREATE_NOTE,
{ clientId: 'my-custom-client' }
]);
calledThis boolean property informs if the mutate function gets called.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [CREATE_NOTE]
);
submit = async (): Promise<void> => {
await this.createNote.mutate(/* variables */);
};
static template = hbs`
<button {{on "click" this.submit}}>
Create Note
</button>
{{#if this.createNote.called}}
Mutate function called.
{{/if}}
`;
}
loadingThis is a handy property that allows us to inform our interface that we are saving data.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [CREATE_NOTE]
);
submit = async (): Promise<void> => {
await this.createNote.mutate(/* variables */);
};
static template = hbs`
<button {{on "click" this.submit}}>
Create Note
</button>
{{#if this.createNote.loading}}
Creating...
{{/if}}
`;
}
errorThis property that can be undefined or an ApolloError object, holds the information about any errors that occurred while executing your mutation. The reported errors are directly reflected from the errorPolicy option available from Apollo Client.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [CREATE_NOTE]
);
submit = async (): Promise<void> => {
await this.createNote.mutate(/* variables */);
};
static template = hbs`
<button {{on "click" this.submit}}>
Create Note
</button>
{{#if this.createNote.error}}
{{this.createNote.error.message}}
{{/if}}
`;
}
As part of the options argument to useMutation, you can pass callback functions
allowing you to execute code when a specific event occurs.
onCompleteThis callback gets called when the mutation completes execution.
createNote = useMutation(this, () => [
CREATE_NOTE,
onComplete: (data): void => {
console.log('Received data:', data);
}
}
]);
onErrorThis callback gets called when we have an error.
createNote = useMutation(this, () => [
CREATE_NOTE,
{
onComplete: (data): void => {
console.log('Received data:', data);
},
onError: (error): void => {
console.error('Received an error:', error.message);
}
}
]);
When you execute a mutation, you modify backend data. If that data is also present in your Apollo Client cache, you might need to update your cache to reflect the result of the mutation. It depends on whether the mutation updates a single existing record.
Apollo will automatically update the record if a mutation updates a single record present in the Apollo Client cache. For this behavior to work, the mutation result must include an id field.
If a mutation modifies multiple entities or creates, deletes records, the Apollo Client cache is not automatically updated to reflect the result of the mutation. To manually update the cache, you can pass an update function to useMutation options.
The purpose of an update function is to modify the cached data to match the modifications that a mutation makes to your backend data without having to refetch the data from your server.
In the example below, we use GetNotes query from the previous section to demonstrate how to update the cache when creating a new note.
import { useMutation } from 'glimmer-apollo';
import {
CREATE_NOTE,
CreateNoteMutation,
CreateNoteMutationVariables
} from './mutations';
import { GET_NOTES, GetNotesQuery, GetNotesQueryVariables } from './queries';
export default class CreateNote extends Component {
createNote = useMutation<CreateNoteMutation, CreateNoteMutationVariables>(
this,
() => [
CREATE_NOTE,
{
update(cache, result): void {
const variables = { isArchived: false };
const data = cache.readQuery<GetNotesQuery, GetNotesQueryVariables>({
query: GET_NOTES,
variables
});
if (data) {
const existingNotes = data.notes;
const newNote = result.data?.createNote;
if (newNote) {
cache.writeQuery({
query: GET_NOTES,
variables,
data: { notes: [newNote, ...existingNotes] }
});
}
}
}
}
]
);
submit = async (): Promise<void> => {
await this.createNote.mutate({
input: {
title: 'Title',
description: 'Description',
isArchived: false
}
});
};
static template = hbs`
<button {{on "click" this.submit}}>
Create Note
</button>
{{#if this.createNote.loading}}
Creating...
{{else if this.createNote.error}}
Error!: {{this.createNote.error.message}}
{{else if this.createNote.called}}
<div>
id: {{this.createNote.data.createNote.id}}
Title: {{this.createNote.data.createNote.title}}
Description: {{this.createNote.data.createNote.description}}
</div>
{{/if}}
`;
}