mutationUpdate
Overview
mutationUpdate
is analogous to the update
option passed to useMutation
.
It is used to update the cache following the execution of a mutation.
Usage
In this example, we have a book library management app. A book is present in two lists stored in the cache, one for all books in the library and one for books written by an author.
First we add the mutation to book.graphql
fragment BookFragment on Book {
id
name
genre
authorId
}
query Books($name: String, $genre: String, $authorId: ID) {
books(name: $name, genre: $genre, authorId: $authorId) {
...BookFragment
}
}
mutation AddBook($book: BookInput!) {
addBook(book: $book) {
...BookFragment
}
}
Then, we define two state slices, one for managing book
state and one for author
state:
bookState
import { state } from '@apollo-orbit/react';
import { AddBookDocument, BooksDocument } from '../../graphql';
export const bookState = state(descriptor => descriptor
.mutationUpdate(AddBookDocument, (cache, info) => {
const addBook = info.data?.addBook;
if (!addBook) return;
cache.updateQuery({ query: BooksDocument }, data => data ? { books: [...data.books, addBook] } : data);
})
);
authorState
import { identifyFragment, state } from '@apollo-orbit/react';
import { AddBookDocument, AuthorFragmentDoc } from '../../graphql';
export const authorState = state(descriptor => descriptor
.mutationUpdate(AddBookDocument, (cache, info) => {
const addBook = info.data?.addBook;
if (!addBook) return;
const authorId = info.variables?.book.authorId as string;
cache.updateFragment(
identifyFragment(AuthorFragmentDoc, authorId),
author => author ? ({ ...author, books: [...author.books, addBook] }) : author
);
})
);
info
argument is of type MutationInfo<AddBookMutationData, AddBookMutationVariables>
and all logic in mutationUpdate
handler is type-safe.
identifyFragment
is a helper function provided by Apollo Orbit for returning a fragment object that uniquely identifies a fragment in the cache.
Provide states
Then, we add the states to ApolloOrbitProvider
as demonstrated previously:
import { authorState } from './library/states/author.state';
import { bookState } from './library/states/book.state';
root.render(
<ApolloProvider client={client}>
<ApolloOrbitProvider states={[authorState, bookState]}>
<App />
</ApolloOrbitProvider>
</ApolloProvider>,
);
Execute mutation
export function Books() {
const [addBook] = useMutation(AddBookDocument);
const handleAddBook = (book: BookInput) => {
addBook({
variables: { book },
update(cache, result) {
const addBook = result.data?.addBook;
if (!addBook) return;
// Update full list of books
cache.updateQuery({ query: BooksDocument }, data => data ? { books: [...data.books, addBook] } : data);
// Update author's list of books
cache.updateFragment(
identifyFragment(AuthorFragmentDoc, book.authorId),
author => author ? ({ ...author, books: [...author.books, addBook] }) : author
);
}
});
};
return (
<>
...
</>
);
}
Now, when a component calls useMutation(AddBookDocument)
, the mutationUpdate
functions in the states are automatically executed and the UI displaying books and author books is updated.
The example above demonstrates how the same mutation can be handled independently by different states, achieving separation of concerns (SoC) and complete decoupling between component and state logic.
mutationUpdate
also accepts the name of the mutation as a string
argument. This can be used in projects that do not have codegen setup.
So the above code can be updated to .mutationUpdate('AddBook', (cache, info) =>
and the rest of the code remains the same, except it won't be type-safe.