Mutations

Unlike queries, mutations in GraphQL are used to create, update or delete data. For this purpose, Draqula offers a useMutation hook. Just like queries, remember to define your GraphQL queries outside your components to prevent infinite unnecessary rerenders.

Basic mutations

Assuming the server implements a ping mutation, that simply returns "pong" string, here's an example of the most basic mutation:

1 const PING_MUTATION = gql`
2 mutation {
3 ping
4 }
5 `;
6
7 const PingPong = () => {
8 const {mutate} = useMutation(PING_MUTATION);
9
10 const onPing = async () => {
11 const data = await mutate();
12 // {
13 // ping: 'pong'
14 // }
15 };
16
17 return <button onClick={onPing}>Ping</button>;
18};

Mutations without variables are not that useful, so let's add some variables to closer match reality.

Variables

Similar to useQuery, useMutation also accepts variables. The only difference is that you don't pass them to useMutation itself, but to the mutate function that it returns.

1 const CREATE_TODO_MUTATION = gql`
2 mutation CreateTodo($title: String!) {
3 createTodo(title: $title) {
4 id
5 title
6 }
7 }
8 `;
9
10const CreateTodo = () => {
11 const [title, setTitle] = useState('');
12 const {mutate} = useMutation(CREATE_TODO_MUTATION);
13
14 const onCreateTodo = async event => {
15 // Prevent the form from refreshing the page
16 event.preventDefault();
17
18 try {
19 await mutate({title});
20
21 // Todo was successfully created
22 } catch (error) {
23 // Uh oh, something went wrong
24 }
25 };
26
27 return (
28 <form onSubmit={onCreateTodo}>
29 <input type="text" value={title} onChange={event => setTitle(event.target.value)} />
30 <br />
31 <button type="submit">Create Todo</button>
32 </form>
33 );
34};

Caching

When a mutation succeeds, Draqula scans the GraphQL types returned from that mutation and refetches all queries that use any of these types. For example, let's say we have the following GraphQL schema on our server:

1 type Query {
2 posts: [Post!]!
3 pages: [Page!]!
4 }
5
6 type Mutation {
7 createPost(title: String!): Post!
8 }
9
10type Post {
11 title: String!
12}
13
14type Page {
15 title: String!
16}

Imagine we also have the following code for our page:

1 const POSTS_QUERY = gql`
2 query {
3 posts {
4 title
5 }
6 }
7 `;
8
9 const PAGES_QUERY = gql`
10 query {
11 pages {
12 title
13 }
14 }
15`;
16
17const CREATE_POST_MUTATION = gql`
18 mutation CreatePost($title: String!) {
19 createPost(title: $title) {
20 title
21 }
22 }
23`;
24
25const Blog = () => {
26 const {data: posts} = useQuery(POSTS_QUERY);
27 const {data: pages} = useQuery(PAGES_QUERY);
28 const {mutate: createPost} = useMutation(CREATE_POST_MUTATION);
29
30 return (
31 <>
32 {/* ... */}
33 <button onClick={() => createPost({title: 'Stranger Things'})}>Create Post</button>
34 </>
35 );
36};

If a user clicks the "Create Post" button, the createPost mutation will be executed, which returns only the Post type. That tells Draqula to find all queries that contain the Post type and refetches them. In this case, POSTS_QUERY is the only one that uses this type, so it will be refetched, while PAGES_QUERY will remain in the same state.

Refetch queries

However, sometimes a server uses the same data source but returns it under various types. In that case, Draqula has no way to know that some query uses the same data under the hood and we need to help it find those queries to refetch. This is where the refetchQueries option comes in. It's a list of queries to refetch in addition to the ones Draqula finds by itself.

So if in theory, PAGES_QUERY needed to be refetched after the createPost mutation, this is how it would work:

1const Blog = () => {
2 const {data: posts} = useQuery(POSTS_QUERY);
3 const {data: pages} = useQuery(PAGES_QUERY);
4 const {mutate: createPost} = useMutation(CREATE_POST_MUTATION, {
5 refetchQueries: [PAGES_QUERY]
6 });
7
8 // ...
9};

It's important to mention that by default createPost doesn't wait for queries to finish refetching. If this behavior is important for you, you can opt-in for that via the waitForRefetchQueries option:

1 const Blog = () => {
2 const {data: posts} = useQuery(POSTS_QUERY);
3 const {data: pages} = useQuery(PAGES_QUERY);
4 const {mutate: createPost} = useMutation(CREATE_POST_MUTATION, {
5 waitForRefetchQueries: true,
6 refetchQueries: [PAGES_QUERY]
7 });
8
9 const onCreatePost = async () => {
10 try {
11 await createPost({title: 'Stranger Things'});
12
13 // Both POSTS_QUERY and PAGES_QUERY are refetched at this point
14 } catch (error) {
15 // ...
16 }
17 };
18
19 // ...
20};

If you wish to disable refetching of any queries, set refetchQueries parameter to false:

1useMutation(CREATE_POST_MUTATION, {
2 refetchQueries: false
3});

API

useMutation(query, options?)

This hook returns a MutationResult object.

query

Type: DocumentNode

Parsed GraphQL query via gql function from the graphql-tag module.

For example:

1const PING_MUTATION = gql`
2 mutation {
3 ping
4 }
5`;

options

Type: object

options.refetchQueries

Type: boolean | DocumentNode[]
Default: []

List of queries to refetch after the mutation succeeds. Set to false to disable any refetching.

options.waitForRefetchQueries

Type: boolean
Default: false

Determines whether to wait for queries to refetch or complete the mutation immediately.

MutationResult

The useMutation hook returns an object consisting of the following fields.

mutate

Type: Function

A function that triggers a mutation. Accepts an optional object of variables to pass to the mutation. Returns a promise that resolves when the mutation is completed.

1const {mutate} = useMutation(CREATE_POST_MUTATION);
2
3const onCreatePost = async () => {
4 await mutate({
5 title: 'New Post'
6 });
7};

data

Type: object

Data returned from the query.

isLoading

Type: boolean
Default: false

Indicates whether the mutation is currently in-flight.

error

Type: NetworkError | GraphQLError | undefined

The error returned from the mutation request. Read more in the "Error handling" section.