【Next.js, GraphQL, Apollo Client】How to rewrite Apollo Client cache after a mutation. -part2-
Tags: nextjs, apolloclient, graphql, typescript
Published: 2024-03-20 01:16:02

In Part 1, I explored how to update the Apollo Client cache using RefetchQueries. However, as mentioned in the article, this approach results in **unnecessary network requests**. In this article, I will demonstrate how to use a **GraphQL Query** to read and rewrite the cache.

## Using GraphQL Query
The official Apollo Client documentation presents various methods for reading and writing to the cache.

Among these, the GraphQL query is a method **for both reading cache and writing new cache data**.

## readQuery
The `readQuery` method is a straightforward way to execute a GraphQL query directly on your cache. As the documentation states:
> The readQuery method enables you to execute a GraphQL query directly on your cache.

In essence, **`readQuery` allows you to retrieve data from the cache** with ease.
Below is an example from the documentation on how to use readQuery:

```javascript
const READ_TODO = gql` //defining the query
  query ReadTodo($id: ID!) {
    todo(id: $id) {
      id
      text
      completed
    }
  }
`;

// Fetch the cached to-do item with ID 5
const { todo } = client.readQuery({
  query: READ_TODO,
  // Provide any required variables in this object.
  // Variables of mismatched types will return `null`.
  variables: {
    id: 5,
  },
});
```

## writeQuery
The `writeQuery` is also a self-explanatory method, according to the documentation:
> The writeQuery method enables you to write data to your cache in a shape that matches a GraphQL query. It resembles readQuery, except that it requires a data option.

Using `writeQuery`, **you can update the cache to ensure that the UI remains synchronized with the data**.

Below is the example use case of writeQuery.

```javascript
const completeTask = async (taskId) => {
  // Optimistically update the cache
  client.writeQuery({
    query: GET_TASKS_QUERY,
    data: {
      tasks: tasks.map(task =>
        task.id === taskId ? { ...task, completed: true } : task
      ),
    },
  });

  // Send the mutation to the server
  try {
    await client.mutate({
      mutation: COMPLETE_TASK_MUTATION,
      variables: { taskId },
    });
  } catch (error) {
    // Rollback the optimistic update if the mutation fails
    client.writeQuery({
      query: GET_TASKS_QUERY,
      data: { tasks },
    });
  }
};
```
In the code example above, when a user completes a task in a to-do list, it optimistically updates the cache using writeQuery, which enables the app to update the UI accordingly. If there is an error in the process of completing the task, the cache is updated again to reflect the error state.

## Update function
In my scenario, the cache needs to be modified immediately after a mutation occurs. Therefore, **I provide an `update` function to useMutation**m in which I use both `readQuery` and `writeQuery`, to manually apply changes to my cached data.

Here is the code.
```javascript
const [registerPiece] = useMutation<RegisterPieceMutationData>(REGISTER_PIECE_MUTATION, {
    update: (cache, { data }) => {
      if (data) {
        const existingData: WardrobeQueryData | null = cache.readQuery({
          query: GET_WARDROBE_QUERY,
        });

        if (existingData) {
          cache.writeQuery({
            query: GET_WARDROBE_QUERY,
            data: { piece: [data.piece, ...existingData.piece] },
          });
        }
      }
    },
```
As an option for `REGISTER_PIECE_MUTATION`, I put update function.

The update function is passed a **cache object **that represents the Apollo Client cache. This object provides access to `readQuery`/`writeQuery` that I mentioned above. Additionally, it is passed data that contains the result of the mutation, which you can use to update the cache with `writeQuery` method.

```javascript
  if (data) {
        const existingData: WardrobeQueryData | null = cache.readQuery({
          query: GET_WARDROBE_QUERY,
        });

```

In the code snippet above, if data is present, the `readQuery` method is triggered, and the result is stored in existingData.

```javascript
if (existingData) {
          cache.writeQuery({
            query: GET_WARDROBE_QUERY,
            data: { piece: [data.piece, ...existingData.piece] },
          });
        }
```
Then, if existing data is not null or undefined, it will execute `writeQuery` method to rewrite the cache.

The result↓
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5pttg4lpkxqf1iksr7j.gif)

Right after the registration of the piece, a user is not required to refresh the page to see the newly registered piece now.

## Conclusion
Although the example in this article demonstrated a straightforward use case for `readQuery` and `writeQuery`, updating the cache can be a bit daunting. If not done correctly, users may encounter confusing or incorrect UI results. Other than GraphQL Queries, there are also GraphQL fragments and the modify method for updating existing cache data or inserting new cache data. I will explore the GraphQL fragment next when I implement update piece detail feature. 

I hope this article has been helpful for those who are just starting out with Apollo Client, like myself :)!!


**Photo Credit**
from:<a href="https://unsplash.com/ja/%E5%86%99%E7%9C%9F/%E6%9C%88%E3%81%AE%E3%83%AC%E3%82%B4%E3%83%AA%E3%82%B9%E3%81%AE%E8%B6%B3%E8%B7%A1-hpt0AJPZ0Aw?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
taken by:<a href="https://unsplash.com/ja/@nasa?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">NASA</a>